Compare commits

...

55 Commits

Author SHA1 Message Date
jmiller dee280a98d fix(theme): repair broken theme + add MokoOnyx surface separation
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 1m0s
Generic: Project CI / Lint & Validate (pull_request) Successful in 34s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Universal: PR Check / Validate PR (pull_request) Successful in 9s
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
Branch Cleanup / Delete merged branch (pull_request) Successful in 2s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
The MokoGitea->MokoGIT branding rebrand deleted the gitea-* theme CSS and
shipped only theme-mokogit-{auto,light,dark}.css, but two things were left
pointing at the retired themes, producing an unstyled "all white" page with an
invisible navbar theme toggle:

1. Users' saved `user.theme` still held gitea-* names, whose CSS now 404s.
   Add migration 370 (RemapGiteaThemesToMokogit) to rewrite each user's saved
   theme gitea-{auto,light,dark}(-colorblind) -> mokogit-* equivalent,
   preserving their light/dark/auto preference. Idempotent.

2. app.example.ini still documented DEFAULT_THEME = gitea-auto. Correct it to
   mokogit-auto so fresh installs start on a shipped theme. (The running
   servers' app.ini [ui] THEMES/DEFAULT_THEME are fixed operationally.)

Also address the flat "all white / no separation" look by adding a MokoOnyx
surface-separation scale to both themes (mokoconsulting.tech gray scale):
- light: soft gray page (#f0f3f6) so white cards/boxes lift off it, gray-200
  box headers, stronger gray-300 border definition.
- dark: 3-step navy-tinted elevation (page #0b1017 -> box #121a24 -> header
  #17212e) cohesive with the #112855 brand nav, more visible panel edges.

The auto theme @imports light/dark, so it inherits both automatically.
2026-07-17 01:58:48 -05:00
jmiller 7174fd0007 fix(cli): report 'mokogit version' instead of 'gitea version'
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Successful in 10s
Generic: Project CI / Lint & Validate (pull_request) Successful in 35s
Generic: Standards Compliance / Secret Scanning (push) Failing after 11s
Generic: Standards Compliance / License Header Validation (push) Successful in 9s
Generic: Standards Compliance / Repository Structure Validation (push) Successful in 10s
Generic: Standards Compliance / Coding Standards Check (push) Successful in 13s
Universal: PR Check / Secret Scan (pull_request) Successful in 1m36s
Deploy (Prod) / Build & Deploy to Prod (push) Failing after 1m27s
Generic: Standards Compliance / Workflow Configuration Check (push) Failing after 11s
Generic: Standards Compliance / Documentation Quality Check (push) Successful in 10s
Generic: Standards Compliance / README Completeness Check (push) Failing after 10s
Generic: Standards Compliance / Script Integrity Validation (push) Successful in 11s
Generic: Standards Compliance / Line Length Check (push) Successful in 22s
Generic: Standards Compliance / Version Consistency Check (push) Successful in 1m15s
Generic: Standards Compliance / File Naming Standards (push) Successful in 10s
Generic: Standards Compliance / Insecure Code Pattern Detection (push) Successful in 10s
Generic: Standards Compliance / Git Repository Hygiene (push) Successful in 1m20s
Generic: Standards Compliance / Dead Code Detection (push) Successful in 13s
Generic: Standards Compliance / File Size Limits (push) Successful in 10s
Generic: Standards Compliance / Code Complexity Analysis (push) Successful in 1m12s
Generic: Standards Compliance / TODO/FIXME Tracking (push) Successful in 9s
Generic: Standards Compliance / Code Duplication Detection (push) Successful in 2m25s
Generic: Standards Compliance / Binary File Detection (push) Successful in 1m58s
Generic: Standards Compliance / Broken Link Detection (push) Successful in 13s
Generic: Standards Compliance / Dependency Vulnerability Scanning (push) Successful in 1m47s
Generic: Standards Compliance / API Documentation Coverage (push) Successful in 10s
Generic: Standards Compliance / Accessibility Check (push) Successful in 11s
Generic: Standards Compliance / Performance Metrics (push) Successful in 11s
Generic: Standards Compliance / Unused Dependencies Check (push) Successful in 1m17s
Generic: Standards Compliance / Terraform Configuration Validation (push) Successful in 18s
Generic: Standards Compliance / Enterprise Readiness Check (push) Successful in 1m4s
Generic: Standards Compliance / Repository Health Check (push) Successful in 1m2s
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: Standards Compliance / Compliance Summary (push) Has been cancelled
The CLI app name (cmd/main.go app.Name) was still 'gitea', so `mokogit --version`
and the USAGE/help printed 'gitea version X' and 'gitea doctor ...'. Rename it to
'mokogit' (lower-cased per the USAGE convention) and rebrand the two adjacent Gitea
CLI strings (program description, --work-path usage).

Verified: go build ./cmd/. Does NOT rename the executable file (still /app/gitea/gitea);
that touches Dockerfile/systemd/deploy paths and is a separate, larger change.
2026-07-17 01:31:06 -05:00
jmiller 46f1a88543 chore: sync rc-revert.yml from Template-Go [skip ci] 2026-07-17 00:45:39 +00:00
jmiller cd026ad481 chore: sync pre-release.yml from Template-Go [skip ci] 2026-07-17 00:45:29 +00:00
jmiller dbb83fd76a chore: sync pr-check.yml from Template-Go [skip ci] 2026-07-17 00:45:18 +00:00
jmiller 2aef425eda chore: sync notify.yml from Template-Go [skip ci] 2026-07-17 00:45:07 +00:00
jmiller 3d118bbc1f chore: sync issue-branch.yml from Template-Go [skip ci] 2026-07-17 00:44:55 +00:00
jmiller 74a865f671 chore: sync cleanup.yml from Template-Go [skip ci] 2026-07-17 00:44:43 +00:00
jmiller d362d7a997 chore: sync ci-issue-reporter.yml from Template-Go [skip ci] 2026-07-17 00:44:31 +00:00
jmiller 98b7b00750 chore: sync ci-generic.yml from Template-Go [skip ci] 2026-07-17 00:44:21 +00:00
jmiller 3cef1d542a chore: sync branch-cleanup.yml from Template-Go [skip ci] 2026-07-17 00:44:10 +00:00
jmiller ab4b89508d chore: sync auto-release.yml from Template-Go [skip ci] 2026-07-17 00:43:59 +00:00
jmiller c688c3d5c4 chore: sync auto-bump.yml from Template-Go [skip ci] 2026-07-17 00:43:48 +00:00
jmiller 81d065b9bb feat: MokoGIT branding -- mokoconsulting.tech reskin, Font Awesome, toggle, a11y menu, PWA
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 32s
Generic: Standards Compliance / Secret Scanning (push) Failing after 9s
Universal: PR Check / Secret Scan (pull_request) Successful in 51s
Generic: Standards Compliance / License Header Validation (push) Successful in 9s
Generic: Standards Compliance / Repository Structure Validation (push) Successful in 8s
Generic: Standards Compliance / Coding Standards Check (push) Successful in 10s
Generic: Standards Compliance / Workflow Configuration Check (push) Failing after 9s
Generic: Standards Compliance / Documentation Quality Check (push) Successful in 8s
Generic: Standards Compliance / README Completeness Check (push) Failing after 7s
Generic: Standards Compliance / Version Consistency Check (push) Successful in 1m2s
Generic: Standards Compliance / Script Integrity Validation (push) Successful in 26s
Generic: Standards Compliance / Git Repository Hygiene (push) Successful in 1m5s
Generic: Standards Compliance / File Naming Standards (push) Successful in 10s
Generic: Standards Compliance / Line Length Check (push) Successful in 23s
Generic: Standards Compliance / Insecure Code Pattern Detection (push) Successful in 10s
Generic: Standards Compliance / Code Complexity Analysis (push) Successful in 1m2s
Generic: Standards Compliance / Code Duplication Detection (push) Successful in 1m8s
Generic: Standards Compliance / Dead Code Detection (push) Successful in 16s
Generic: Standards Compliance / File Size Limits (push) Successful in 16s
Generic: Standards Compliance / TODO/FIXME Tracking (push) Successful in 14s
Generic: Standards Compliance / Dependency Vulnerability Scanning (push) Successful in 1m3s
Deploy (Prod) / Build & Deploy to Prod (push) Successful in 5m9s
Generic: Standards Compliance / Broken Link Detection (push) Successful in 12s
Generic: Standards Compliance / API Documentation Coverage (push) Successful in 7s
Generic: Standards Compliance / Accessibility Check (push) Successful in 7s
Generic: Standards Compliance / Performance Metrics (push) Successful in 8s
Generic: Standards Compliance / Binary File Detection (push) Successful in 2m12s
Generic: Standards Compliance / Unused Dependencies Check (push) Successful in 55s
Generic: Standards Compliance / Terraform Configuration Validation (push) Successful in 12s
Generic: Standards Compliance / Enterprise Readiness Check (push) Successful in 55s
Generic: Standards Compliance / Repository Health Check (push) Successful in 54s
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Standards Compliance / Compliance Summary (push) Has been cancelled
Comprehensive MokoGIT theme/branding built from the authoritative mokoconsulting.tech
design tokens (MokoOnyx *.custom.css):

- Theme: replace gitea themes entirely with theme-mokogit-{light,dark,auto} (default
  mokogit-auto); brand tokens (primary #010156 / accent #3f8ff0, navy nav+footer #112855,
  semantic palette); mokogit-brand.css component reskin; runtime custom-stylesheet override
  hook (templates/custom/header.tmpl -> mokogit-custom.css, re-skin live with no rebuild).
- Font Awesome 7 Free 7.1.0: vendored unmodified (CSS headers intact + webfonts + verbatim
  LICENSE.txt), license-compliant for distribution/white-label (Icons CC BY 4.0, Fonts SIL
  OFL 1.1, Code MIT); attributed in THIRD-PARTY-NOTICES.md, README, and wiki.
- Light/dark toggle: navbar sun/moon control, server-persisted via the existing
  /user/settings/appearance/theme endpoint (anonymous falls back to localStorage).
- Accessibility menu: 6 options (text resize, color invert, high contrast, link highlight,
  readable font, pause animations); localStorage-persisted, applied on <html>, ARIA/keyboard.
- PWA: service worker (network-first navigation + cached offline fallback) + branded offline
  page + enhanced manifest (display standalone, navy theme_color, app shortcuts); manifest
  icon sourced from the site-admin branding icon if set, else the bundled MokoGIT logo.

Verified: `go build ./...` and `vite build` both green; only mokogit themes emit.
2026-07-16 14:31:52 -05:00
jmiller c5b15a8ea9 fix(build): report the xx.xx.xx VERSION in Docker builds (not git describe)
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Generic: Project CI / Lint & Validate (pull_request) Successful in 40s
Universal: PR Check / Validate PR (pull_request) Successful in 11s
Universal: PR Check / Secret Scan (pull_request) Successful in 58s
Generic: Standards Compliance / Secret Scanning (push) Failing after 12s
Generic: Standards Compliance / License Header Validation (push) Successful in 11s
Generic: Standards Compliance / Repository Structure Validation (push) Successful in 11s
Generic: Standards Compliance / Coding Standards Check (push) Successful in 15s
Generic: Standards Compliance / Workflow Configuration Check (push) Failing after 11s
Generic: Standards Compliance / Documentation Quality Check (push) Successful in 11s
Generic: Standards Compliance / README Completeness Check (push) Failing after 14s
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: 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: Standards Compliance / Compliance Summary (push) Has been cancelled
Generic: Standards Compliance / Version Consistency Check (push) Successful in 1m28s
Generic: Standards Compliance / Git Repository Hygiene (push) Successful in 1m17s
Deploy (Prod) / Build & Deploy to Prod (push) Successful in 6m21s
The Dockerfile bind-mounts .git for `make backend` ("required for version data"),
so the Makefile computed the binary version via `git describe` — the leaked upstream
Gitea version (e.g. 1.26.1+moko...-967-gHASH) — even though a VERSION file (01.00.00)
is committed. The Makefile's STORED_VERSION (VERSION file) takes precedence over
`git describe`, but .dockerignore excluded `/VERSION`, so the build never saw it.

Stop excluding VERSION from the Docker build context. STORED_VERSION=01.00.00 now
wins and the built binary reports the MokoOrgStandards xx.xx.xx version. (Upstream
excludes VERSION because it's normally a generated release-tarball artifact; MokoGIT
commits it intentionally as the version source.)

Verify after deploy: the footer and /api/v1/version report 01.00.00, not a git-describe
string. Completes the versioning adoption whose repo-side pieces (VERSION file,
update-checker default off) were already in place.
2026-07-16 13:44:58 -05:00
jmiller 3267fa42b1 chore: sync issue-branch.yml from Template-Go [skip ci] 2026-07-16 18:37:20 +00:00
jmiller 8684ce0e3e chore: sync branch-cleanup.yml from Template-Go [skip ci] 2026-07-16 18:36:43 +00:00
jmiller dbcbff79ee merge(main): promote dev to production
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Successful in 11s
Generic: Project CI / Lint & Validate (pull_request) Successful in 33s
Generic: Standards Compliance / Secret Scanning (push) Failing after 10s
Generic: Standards Compliance / License Header Validation (push) Successful in 8s
Universal: PR Check / Secret Scan (pull_request) Successful in 50s
Generic: Standards Compliance / Repository Structure Validation (push) Successful in 9s
Generic: Standards Compliance / Coding Standards Check (push) Successful in 11s
Generic: Standards Compliance / Workflow Configuration Check (push) Failing after 10s
Generic: Standards Compliance / Documentation Quality Check (push) Successful in 10s
Generic: Standards Compliance / README Completeness Check (push) Failing after 10s
Generic: Standards Compliance / Version Consistency Check (push) Successful in 1m22s
Generic: Standards Compliance / Git Repository Hygiene (push) Successful in 55s
Generic: Standards Compliance / Script Integrity Validation (push) Successful in 13s
Generic: Standards Compliance / File Naming Standards (push) Successful in 8s
Generic: Standards Compliance / Line Length Check (push) Successful in 23s
Generic: Standards Compliance / Insecure Code Pattern Detection (push) Successful in 9s
Generic: Standards Compliance / Code Duplication Detection (push) Successful in 1m24s
Generic: Standards Compliance / Code Complexity Analysis (push) Successful in 1m27s
Generic: Standards Compliance / File Size Limits (push) Successful in 13s
Generic: Standards Compliance / Dead Code Detection (push) Successful in 15s
Generic: Standards Compliance / TODO/FIXME Tracking (push) Successful in 9s
Deploy (Prod) / Build & Deploy to Prod (push) Successful in 5m8s
Generic: Standards Compliance / Dependency Vulnerability Scanning (push) Successful in 53s
Generic: Standards Compliance / Broken Link Detection (push) Successful in 11s
Generic: Standards Compliance / API Documentation Coverage (push) Successful in 7s
Generic: Standards Compliance / Accessibility Check (push) Successful in 7s
Generic: Standards Compliance / Performance Metrics (push) Successful in 7s
Generic: Standards Compliance / Unused Dependencies Check (push) Successful in 53s
Generic: Standards Compliance / Binary File Detection (push) Successful in 1m46s
Generic: Standards Compliance / Terraform Configuration Validation (push) Successful in 12s
Generic: Standards Compliance / Enterprise Readiness Check (push) Successful in 54s
Generic: Standards Compliance / Repository Health Check (push) Successful in 57s
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: Standards Compliance / Compliance Summary (push) Has been cancelled
Session work: #798 WorkflowDirs (detect .mokogit/workflows, retire .mokogitea),
org internal-wiki merge, issue custom-status dropdown visibility fix, MokoOrgStandards
xx.xx.xx versioning + .vault deploy staging, and restored .mokogit/workflows from
Template-Go.

Conflict resolution: main had dropped .mokogit/workflows/* (4885f3c1 "drop orphaned
dupes"); kept dev's restored set. main's rebrand + prod hotfixes (#793/#794/#795,
.mokogitea config-dir removal) are preserved. CHANGELOG updated.
2026-07-16 13:04:35 -05:00
jmiller 42ea214a64 chore(ci): restore .mokogit/workflows from Template-Go (sync was broken)
Universal: Auto Version Bump / Version Bump (push) Successful in 15s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 1m26s
Deploy (Dev) / Build & Deploy to Dev (push) Successful in 4m30s
The MokoGIT repo's dev/main branches had lost all .mokogit/workflows/ files
(0 present in any workflows dir), which — compounded with the #798 workflow-
discovery break — left the repo with no CI/deploy. The workflow-sync (Template-Go
-> repo .mokogit/workflows) appears to have stopped; since the sync trigger lives
in .mokogit/workflows itself, broken discovery prevented it from self-healing.

Restore the full canonical set from Template-Go (deploy-{dev,rc,prod}, pr-check,
standards-compliance, auto-release, pre-release, auto-bump, cascade-dev,
version-set, repo-health, custom/workflow-sync-trigger, etc.). deploy-* are the
portable Go image-build workflows (Dockerfile build -> registry -> recreate).
2026-07-16 12:50:33 -05:00
jmiller d16b417a24 fix(issues): make custom-status select render its options (visibility bug)
The issue-view "status" control was a native <select> carrying the fomantic
"ui compact dropdown" class. Fomantic enhanced it into an overlay menu that
rendered mispositioned and stacked BEHIND .issue-content, so the status options
were present in the DOM but invisible (a raised z-index did not rescue it — the
menu was trapped in an ancestor stacking context). Nothing in web_src/js depends
on it being a fomantic dropdown.

Drop the fomantic class so it stays a plain native <select> (the browser renders
its option list, so the options always show), and move the element's inline
style= into a new stylesheet web_src/css/repo/issue-status.css (imported from
index.css), per the "no inline styles" standard.
2026-07-16 12:28:57 -05:00
jmiller fdde590491 fix(actions): detect .mokogit/workflows; retire .mokogitea/workflows (#798)
The deployed binary's WorkflowDirs scanned only .mokogitea/workflows (the
temporary cutover name) plus .gitea/.github, but every repo moved its CI to
the rebranded .mokogit/workflows — so those workflows were never indexed, no
runs were created on push, and workflow_dispatch 404'd. Org-wide CI/deploys
were down, surviving only via a temporary .gitea/workflows bridge.

Set the default WorkflowDirs to [.mokogit/workflows, .gitea/workflows,
.github/workflows] and drop .mokogitea/workflows entirely (fully retired).
Runtime override remains available via [actions] WORKFLOW_DIRS.

Once deployed, the .gitea/workflows deploy bridge can be retired.

Updated Test_WorkflowDirs default expectation.
2026-07-16 11:56:23 -05:00
jmiller 2708f61a53 merge(dev): org internal-wiki (Overview renders wiki, drop Wiki tab) 2026-07-16 11:56:18 -05:00
jmiller 55639a6453 merge(dev): .vault deploy staging + xx.xx.xx versioning (feat/vault-align-versioning) 2026-07-16 11:56:14 -05:00
jmiller afc3fc6f16 feat(org): merge profile into internal wiki — Overview renders wiki, drop Wiki tab
Streamlines the org WebUI now that the profile and wiki share the same
.mokogit / .mokogit-private repos:

- Org Overview renders the internal wiki's landing page (resolution order
  home -> index -> readme -> profile) from the .mokogit (public) /
  .mokogit-private (members-only) repo wiki, respecting the existing
  public/members view split. Safe fallback: if the wiki or a landing page is
  absent or errors, it falls back to the legacy root README.md (never 500s).
- Hide the per-repo Wiki tab for .mokogit / .mokogit-private, since their wiki
  is now surfaced as the org Overview.
- Relabel the existing org-settings "Internal wiki" control from .profile ->
  .mokogit / .mokogit-private (backed by the existing WikiMode field/handler).

go build ./... green.
2026-07-16 11:45:08 -05:00
jmiller 0cb94ef37d chore(deploy): stage .vault git-stack compose + adopt xx.xx.xx versioning
Repo-only prep to align MokoGIT with the .vault single-source-of-truth structure and the
MokoOrgStandards xx.xx.xx version scheme. No server/.vault changes; relocation coordinated later.

Versioning (replaces leaked Gitea 1.24.0+dev-N-ghash):
- VERSION = 01.00.00 (Makefile STORED_VERSION uses it verbatim on branch builds -> strips the
  git-describe Gitea base; tag builds use the xx.xx.xx tag name).
- VERSIONING.md documents the scheme + how the binary/image version resolves.
- Default the upstream update-checker OFF (modules/setting/setting.go) so the fork never compares
  AppVer against Gitea's release feed.

Staged git-stack compose (target .vault/stacks/git/{prod,rc,dev}):
- deploy/git/{prod,rc,dev}/ each: docker-compose.yml (image mokoconsulting/mokogit per tier tag env
  MOKOGIT[_RC|_DEV]_TAG, containers mokogit/-rc/-dev, /opt/mokogit/{prod,rc,dev} data, host-MySQL
  db mokogit[_rc|_dev], /api/healthz healthcheck), VERSION (01.00.00), .env.example.
- deploy/git/VERSION (per-stack) + README.md (ownership split, reconcile-before-drop, restructure paused).
- Every host/secret value is ${VAR}; modeled on documented live config, marked RECONCILE (diff vs
  the live /opt/mokogitea/docker-compose.yml before dropping into .vault).

Secrets externalized:
- .gitignore now ignores real .env / *.env but keeps *.env.example. No real tokens committed.

Verified: go build ./modules/setting/ ok; .env.example tracked, real .env ignored.
2026-07-16 10:43:18 -05:00
jmiller 6f482e7fdb Merge pull request 'fix: remove stale .mokogitea from dev (MOKOGIT migration)' (#797) from fix/remove-mokogitea-dev into dev 2026-07-16 14:09:43 +00:00
jmiller 1be5a89286 fix: remove stale .mokogitea/workflows/version-set.yml 2026-07-16 14:09:31 +00:00
jmiller 675a14615a fix: remove stale .mokogitea/workflows/standards-compliance.yml 2026-07-16 14:09:19 +00:00
jmiller 2d14619461 fix: remove stale .mokogitea/workflows/repo-health.yml 2026-07-16 14:09:03 +00:00
jmiller ca37d9728e fix: remove stale .mokogitea/workflows/rc-revert.yml 2026-07-16 14:08:49 +00:00
jmiller 232e7bcf83 fix: remove stale .mokogitea/workflows/pre-release.yml 2026-07-16 14:08:33 +00:00
jmiller 5de6ace021 fix: remove stale .mokogitea/workflows/pr-check.yml
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Failing after 4s
2026-07-16 14:08:20 +00:00
jmiller dd864c2268 fix: remove stale .mokogitea/workflows/notify.yml
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Failing after 5s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Successful in 9s
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
2026-07-16 14:08:07 +00:00
jmiller 245a74024b fix: remove stale .mokogitea/workflows/issue-branch.yml
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Failing after 4s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Successful in 9s
Universal: PR Check / Secret Scan (pull_request) Successful in 49s
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
2026-07-16 14:07:52 +00:00
jmiller ec913d7993 fix: remove stale .mokogitea/workflows/gitleaks.yml
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Failing after 5s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Successful in 12s
Universal: PR Check / Secret Scan (pull_request) Successful in 52s
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
2026-07-16 14:07:38 +00:00
jmiller 8ff637684a fix: remove stale .mokogitea/workflows/deploy-rc.yml
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Failing after 4s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Successful in 10s
Universal: PR Check / Secret Scan (pull_request) Successful in 54s
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
2026-07-16 14:07:24 +00:00
jmiller d0e39c027b fix: remove stale .mokogitea/workflows/deploy-prod.yml
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Failing after 5s
2026-07-16 14:07:09 +00:00
jmiller acefbe6e44 fix: remove stale .mokogitea/workflows/deploy-dev.yml
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Failing after 7s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Successful in 11s
Universal: PR Check / Secret Scan (pull_request) Successful in 56s
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
2026-07-16 14:06:55 +00:00
jmiller f430a4c10d fix: remove stale .mokogitea/workflows/custom/upstream-bug-sync.yml
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Failing after 4s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Universal: PR Check / Validate PR (pull_request) Successful in 11s
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
2026-07-16 14:06:39 +00:00
jmiller db1d97072a fix: remove stale .mokogitea/workflows/custom/test-mokogitea.yml
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Failing after 4s
Universal: PR Check / Branch Policy (pull_request) Successful in 3s
Universal: PR Check / Validate PR (pull_request) Successful in 10s
Universal: PR Check / Secret Scan (pull_request) Successful in 1m0s
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
2026-07-16 14:06:25 +00:00
jmiller d37da15aa7 fix: remove stale .mokogitea/workflows/custom/pr-rc-release.yml 2026-07-16 14:05:06 +00:00
jmiller f31a60cf53 fix: remove stale .mokogitea/workflows/custom/cascade-dev.yml
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Failing after 5s
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 11s
Universal: PR Check / Secret Scan (pull_request) Successful in 1m0s
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
2026-07-16 14:04:51 +00:00
jmiller 871ede1450 fix: remove stale .mokogitea/workflows/cleanup.yml
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Failing after 5s
PR RC Release / Build RC Release (pull_request) Successful in 2s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Successful in 11s
Universal: PR Check / Secret Scan (pull_request) Successful in 1m1s
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
2026-07-16 14:04:38 +00:00
jmiller e16ea6f934 fix: remove stale .mokogitea/workflows/ci-issue-reporter.yml
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Failing after 4s
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 9s
Universal: PR Check / Secret Scan (pull_request) Successful in 56s
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
2026-07-16 14:04:22 +00:00
jmiller c610c2388d fix: remove stale .mokogitea/workflows/ci-generic.yml
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Failing after 5s
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 9s
Universal: PR Check / Secret Scan (pull_request) Successful in 53s
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
2026-07-16 14:04:08 +00:00
jmiller cb2ff768e5 fix: remove stale .mokogitea/workflows/cascade-dev.yml
PR RC Release / Build RC Release (pull_request) Successful in 3s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Project CI / Lint & Validate (pull_request) Successful in 29s
Universal: PR Check / Validate PR (pull_request) Successful in 9s
Universal: PR Check / Secret Scan (pull_request) Successful in 51s
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-16 14:03:53 +00:00
jmiller 78f1d8fe15 fix: remove stale .mokogitea/workflows/branch-cleanup.yml
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Failing after 5s
PR RC Release / Build RC Release (pull_request) Successful in 4s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Generic: Project CI / Lint & Validate (pull_request) Successful in 30s
Universal: PR Check / Validate PR (pull_request) Successful in 11s
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
2026-07-16 14:03:40 +00:00
jmiller 8c121c08c1 fix: remove stale .mokogitea/workflows/auto-release.yml
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Failing after 5s
PR RC Release / Build RC Release (pull_request) Successful in 3s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Generic: Project CI / Lint & Validate (pull_request) Successful in 30s
Universal: PR Check / Validate PR (pull_request) Successful in 12s
Universal: PR Check / Secret Scan (pull_request) Successful in 1m3s
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-16 14:03:30 +00:00
jmiller 423c3c0f83 fix: remove stale .mokogitea/workflows/auto-bump.yml
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Failing after 3s
PR RC Release / Build RC Release (pull_request) Successful in 2s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Successful in 11s
Generic: Project CI / Lint & Validate (pull_request) Successful in 38s
Universal: PR Check / Secret Scan (pull_request) Successful in 1m5s
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-16 14:03:19 +00:00
jmiller 411bed0d05 Merge pull request 'fix: remove stale .mokogitea config dir (superseded by .mokogit)' (#795) from fix/migrate-mokogitea-to-mokogit into main 2026-07-16 13:39:30 +00:00
jmiller 7a71ebd0b1 fix: remove stale .mokogitea config dir (superseded by .mokogit) 2026-07-16 07:34:27 -05:00
jmiller 6dd0f1fb6d Merge pull request 'fix(repo): case-sensitive system-repo name normalization' (#794) from rc into main
Generic: Standards Compliance / Secret Scanning (push) Failing after 15s
Generic: Standards Compliance / License Header Validation (push) Successful in 12s
Generic: Standards Compliance / Repository Structure Validation (push) Successful in 10s
Generic: Standards Compliance / Coding Standards Check (push) Successful in 12s
Generic: Standards Compliance / Version Consistency Check (push) Successful in 1m28s
Generic: Standards Compliance / Workflow Configuration Check (push) Failing after 12s
Generic: Standards Compliance / Documentation Quality Check (push) Successful in 13s
Generic: Standards Compliance / README Completeness Check (push) Failing after 22s
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
Generic: Standards Compliance / Git Repository Hygiene (push) Successful in 2m16s
Deploy (Prod) / Build & Deploy to Prod (push) Successful in 7m2s
2026-07-15 15:21:21 +00:00
jmiller 1f73a14cb0 Merge pull request 'feat: MokoGitea -> MokoGIT rebrand (validated on dev + rc)' (#793) from rc into main
Generic: Standards Compliance / Secret Scanning (push) Failing after 8s
Generic: Standards Compliance / License Header Validation (push) Successful in 9s
Generic: Standards Compliance / Repository Structure Validation (push) Successful in 10s
Generic: Standards Compliance / Coding Standards Check (push) Successful in 11s
Generic: Standards Compliance / Version Consistency Check (push) Successful in 1m15s
Generic: Standards Compliance / Workflow Configuration Check (push) Failing after 10s
Generic: Standards Compliance / Documentation Quality Check (push) Successful in 8s
Generic: Standards Compliance / README Completeness Check (push) Failing after 10s
Generic: Standards Compliance / Git Repository Hygiene (push) Successful in 2m1s
Generic: Standards Compliance / Script Integrity Validation (push) Successful in 24s
Generic: Standards Compliance / Line Length Check (push) Successful in 33s
Generic: Standards Compliance / File Naming Standards (push) Successful in 9s
Generic: Standards Compliance / Insecure Code Pattern Detection (push) Successful in 8s
Deploy (Prod) / Build & Deploy to Prod (push) Successful in 6m15s
Generic: Standards Compliance / Dead Code Detection (push) Successful in 10s
Generic: Standards Compliance / File Size Limits (push) Successful in 8s
Generic: Standards Compliance / Code Complexity Analysis (push) Successful in 1m8s
Generic: Standards Compliance / TODO/FIXME Tracking (push) Successful in 8s
Generic: Standards Compliance / Code Duplication Detection (push) Successful in 55s
Generic: Standards Compliance / Dependency Vulnerability Scanning (push) Successful in 57s
Generic: Standards Compliance / Unused Dependencies Check (push) Successful in 54s
Generic: Standards Compliance / API Documentation Coverage (push) Successful in 8s
Generic: Standards Compliance / Broken Link Detection (push) Successful in 11s
Generic: Standards Compliance / Binary File Detection (push) Successful in 1m45s
Generic: Standards Compliance / Accessibility Check (push) Successful in 8s
Generic: Standards Compliance / Performance Metrics (push) Successful in 8s
Generic: Standards Compliance / Terraform Configuration Validation (push) Successful in 14s
Generic: Standards Compliance / Repository Health Check (push) Successful in 57s
Generic: Standards Compliance / Enterprise Readiness Check (push) Successful in 1m0s
Generic: Standards Compliance / Compliance Summary (push) Has been cancelled
2026-07-15 06:05:54 +00:00
jmiller 2c0d71a27f chore(ci): re-trigger dev deploy for MokoGIT rebrand
Universal: Auto Version Bump / Version Bump (push) Successful in 15s
Deploy (Dev) / Build & Deploy to Dev (push) Successful in 4m56s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
PR RC Release / Build RC Release (pull_request) Successful in 1s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Failing after 3s
Universal: PR Check / Validate PR (pull_request) Successful in 8s
Generic: Project CI / Lint & Validate (pull_request) Successful in 31s
Universal: PR Check / Secret Scan (pull_request) Successful in 42s
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-15 04:06:18 +00:00
jmiller 584c50a4be Merge pull request 'feat: MokoGitea -> MokoGIT rebrand (module rename, branding, .mokogit, icon)' (#791) from feature/rebrand-mokogit into dev 2026-07-15 03:07:38 +00:00
86 changed files with 1756 additions and 703 deletions
+6 -1
View File
@@ -63,7 +63,12 @@ cpu.out
/public/assets/fonts
/public/assets/img/avatar
/vendor
/VERSION
# MokoGIT: keep the committed VERSION file in the Docker build context. The Makefile
# reads it as STORED_VERSION, which takes precedence over `git describe`, so the built
# binary reports the MokoOrgStandards xx.xx.xx version (e.g. 01.00.00) instead of the
# leaked upstream Gitea `git describe` version. (Upstream excludes it because VERSION is
# normally a generated release-tarball artifact; here it is intentionally committed.)
# /VERSION
/.air
/.go-licenses
/Dockerfile
+7
View File
@@ -115,6 +115,13 @@ prime/
/.github/copilot-instructions.md
/llms.txt
# Real deploy env files -- NEVER commit secrets. Keep the *.env.example templates.
.env
*.env
!*.env.example
!.env.example
deploy/**/.env
# Ignore worktrees when working on multiple branches
.worktrees/
@@ -6,7 +6,7 @@
# DEFGROUP: MokoGIT.Workflow
# INGROUP: mokocli.Release
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# PATH: /.mokogitea/workflows/auto-bump.yml
# PATH: /.mokogit/workflows/auto-bump.yml
# VERSION: 09.02.00
# BRIEF: Auto patch-bump version on every push to dev (skips merge commits)
@@ -22,7 +22,7 @@ on:
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
MOKOGITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
MOKOGIT_URL: ${{ vars.MOKOGIT_URL || 'https://git.mokoconsulting.tech' }}
permissions:
contents: write
@@ -40,7 +40,7 @@ jobs:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
token: ${{ secrets.MOKOGITEA_TOKEN }}
token: ${{ secrets.MOKOGIT_TOKEN }}
fetch-depth: 1
- name: Setup mokocli tools
@@ -52,7 +52,7 @@ jobs:
echo "MOKO_CLI=/opt/mokocli/cli" >> "$GITHUB_ENV"
else
git clone --depth 1 --branch main --quiet \
"https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/MokoConsulting/mokocli.git" \
"https://x-access-token:${{ secrets.MOKOGIT_TOKEN }}@git.mokoconsulting.tech/MokoConsulting/mokocli.git" \
/tmp/mokocli
cd /tmp/mokocli && composer install --no-dev --no-interaction --quiet
echo "MOKO_CLI=/tmp/mokocli/cli" >> "$GITHUB_ENV"
@@ -62,5 +62,5 @@ jobs:
run: |
php ${MOKO_CLI}/version_auto_bump.php \
--path . --branch "${GITHUB_REF_NAME}" \
--token "${{ secrets.MOKOGITEA_TOKEN }}" \
--repo-url "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
--token "${{ secrets.MOKOGIT_TOKEN }}" \
--repo-url "https://x-access-token:${{ secrets.MOKOGIT_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
@@ -31,7 +31,7 @@ on:
branches:
- main
paths-ignore:
- '.mokogitea/workflows/**'
- '.mokogit/workflows/**'
- '*.md'
- 'wiki/**'
- '.editorconfig'
@@ -52,9 +52,9 @@ on:
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
MOKOGITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
GITEA_ORG: ${{ vars.GITEA_ORG || github.repository_owner }}
GITEA_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }}
MOKOGIT_URL: ${{ vars.MOKOGIT_URL || 'https://git.mokoconsulting.tech' }}
GIT_ORG: ${{ vars.MOKOGIT_ORG || github.repository_owner }}
GIT_REPO: ${{ vars.MOKOGIT_REPO || github.event.repository.name }}
permissions:
contents: write
@@ -73,12 +73,12 @@ jobs:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
token: ${{ secrets.MOKOGITEA_TOKEN }}
token: ${{ secrets.MOKOGIT_TOKEN }}
fetch-depth: 1
- name: Setup mokocli tools
env:
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
run: |
if [ -f /opt/mokocli/cli/version_bump.php ] && [ -f /opt/mokocli/vendor/autoload.php ]; then
@@ -99,8 +99,8 @@ jobs:
- name: Rename branch to rc
run: |
API_BASE="${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
AUTH="Authorization: token ${{ secrets.MOKOGITEA_TOKEN }}"
API_BASE="${MOKOGIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}"
AUTH="Authorization: token ${{ secrets.MOKOGIT_TOKEN }}"
FROM="${{ github.event.pull_request.head.ref || 'dev' }}"
PR="${{ github.event.pull_request.number }}"
@@ -110,11 +110,11 @@ 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 via git push. Gitea's git/refs PATCH API
# Point rc at the source commit via git push. Git'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"
PUSH_URL="https://x-access-token:${{ secrets.MOKOGIT_TOKEN }}@${MOKOGIT_URL#https://}/${GIT_ORG}/${GIT_REPO}.git"
git config --global user.name "mokogit-actions[bot]"
git config --global user.email "actions@mokoconsulting.tech"
git fetch --no-tags "$PUSH_URL" "${FROM}"
@@ -139,7 +139,7 @@ jobs:
- name: Trigger RC deploy
run: |
# Workflow-token pushes do NOT wake downstream workflows; dispatch deploy-rc explicitly.
curl -sf -X POST -H "Authorization: token ${{ secrets.MOKOGITEA_TOKEN }}" \n -H "Content-Type: application/json" \n "${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}/actions/workflows/deploy-rc.yml/dispatches" \n -d '{"ref":"rc"}' \n && echo "Dispatched deploy-rc on rc" \n || echo "::warning::deploy-rc dispatch failed (no deploy-rc.yml on rc?)"
curl -sf -X POST -H "Authorization: token ${{ secrets.MOKOGIT_TOKEN }}" \n -H "Content-Type: application/json" \n "${MOKOGIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}/actions/workflows/deploy-rc.yml/dispatches" \n -d '{"ref":"rc"}' \n && echo "Dispatched deploy-rc on rc" \n || echo "::warning::deploy-rc dispatch failed (no deploy-rc.yml on rc?)"
- name: Checkout rc and configure git
run: |
@@ -147,20 +147,20 @@ jobs:
git checkout rc
git config --local user.email "mokogit-actions[bot]@mokoconsulting.tech"
git config --local user.name "mokogit-actions[bot]"
git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
git remote set-url origin "https://x-access-token:${{ secrets.MOKOGIT_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
- name: Publish RC release
continue-on-error: true
run: |
php ${MOKO_CLI}/release_publish.php \
--path . --stability rc --bump minor --branch rc \
--token "${{ secrets.MOKOGITEA_TOKEN }}"
--token "${{ secrets.MOKOGIT_TOKEN }}"
- name: Update RC release notes from CHANGELOG.md
continue-on-error: true
run: |
API_BASE="${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
API_BASE="${MOKOGIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}"
TOKEN="${{ secrets.MOKOGIT_TOKEN }}"
# Extract [Unreleased] section from changelog
NOTES=""
@@ -209,14 +209,14 @@ jobs:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
token: ${{ secrets.MOKOGITEA_TOKEN }}
token: ${{ secrets.MOKOGIT_TOKEN }}
fetch-depth: 0
- name: Configure git for bot pushes
run: |
git config --local user.email "mokogit-actions[bot]@mokoconsulting.tech"
git config --local user.name "mokogit-actions[bot]"
git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
git remote set-url origin "https://x-access-token:${{ secrets.MOKOGIT_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
- name: Check for merge conflict markers
run: |
@@ -233,7 +233,7 @@ jobs:
- name: Setup mokocli tools
env:
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_MIRROR_TOKEN }}"}}'
run: |
@@ -280,7 +280,7 @@ jobs:
fi
php ${MOKO_CLI}/release_publish.php \
--path . --stability stable ${BUMP_FLAG} --branch main \
--token "${{ secrets.MOKOGITEA_TOKEN }}"
--token "${{ secrets.MOKOGIT_TOKEN }}"
- name: "Read published version"
id: version
@@ -307,8 +307,8 @@ jobs:
!startsWith(steps.platform.outputs.platform, 'joomla')
run: |
VERSION="${{ steps.version.outputs.version }}"
API_BASE="${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
API_BASE="${MOKOGIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}"
TOKEN="${{ secrets.MOKOGIT_TOKEN }}"
SEMVER_TAG="v${VERSION}"
echo "Creating semver tag: ${SEMVER_TAG}"
@@ -332,8 +332,8 @@ jobs:
- name: Update release notes and promote changelog
run: |
API_BASE="${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
API_BASE="${MOKOGIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}"
TOKEN="${{ secrets.MOKOGIT_TOKEN }}"
# Get the stable release info (version and ID)
RELEASE_JSON=$(curl -sf -H "Authorization: token ${TOKEN}" \
@@ -401,10 +401,10 @@ jobs:
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
GH_REPO="${{ vars.GH_MIRROR_REPO || github.repository }}"
API_BASE="${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
API_BASE="${MOKOGIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}"
php ${MOKO_CLI}/release_mirror.php \
--version "$VERSION" --tag "$RELEASE_TAG" \
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
--token "${{ secrets.MOKOGIT_TOKEN }}" --api-base "$API_BASE" \
--gh-token "${{ secrets.GH_MIRROR_TOKEN }}" --gh-repo "$GH_REPO" \
--branch main 2>&1 || true
echo "GitHub mirror updated" >> $GITHUB_STEP_SUMMARY
@@ -430,8 +430,8 @@ jobs:
if: steps.version.outputs.skip != 'true'
continue-on-error: true
run: |
API_BASE="${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
API_BASE="${MOKOGIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}"
TOKEN="${{ secrets.MOKOGIT_TOKEN }}"
# Delete rc branch (ephemeral — created by promote-rc)
curl -sf -X DELETE -H "Authorization: token ${TOKEN}" \
@@ -454,8 +454,8 @@ jobs:
if: steps.version.outputs.skip != 'true'
continue-on-error: true
run: |
API_BASE="${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
API_BASE="${MOKOGIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}"
TOKEN="${{ secrets.MOKOGIT_TOKEN }}"
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
BRANCH_NAME="version/${VERSION}"
MAIN_SHA=$(git rev-parse HEAD)
@@ -475,9 +475,9 @@ jobs:
if: steps.version.outputs.skip != 'true'
continue-on-error: true
run: |
API_BASE="${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
API_BASE="${MOKOGIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}"
php ${MOKO_CLI}/version_reset_dev.php \
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "${API_BASE}" \
--token "${{ secrets.MOKOGIT_TOKEN }}" --api-base "${API_BASE}" \
--branch dev --path . 2>&1 || true
# -- Summary --------------------------------------------------------------
@@ -501,5 +501,5 @@ jobs:
echo "| Version | \`${VERSION}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Branch | \`${{ steps.version.outputs.branch }}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Tag | \`${{ steps.version.outputs.tag }}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Release | [View](${MOKOGITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/tag/${{ steps.version.outputs.tag }}) |" >> $GITHUB_STEP_SUMMARY
echo "| Release | [View](${MOKOGIT_URL}/${GIT_ORG}/${GIT_REPO}/releases/tag/${{ steps.version.outputs.tag }}) |" >> $GITHUB_STEP_SUMMARY
fi
@@ -4,9 +4,9 @@
#
# FILE INFORMATION
# DEFGROUP: MokoGIT.Workflow
# INGROUP: MokoStandards.Universal
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# PATH: /.mokogitea/workflows/branch-cleanup.yml
# INGROUP: MokoCLI.Universal
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic
# PATH: /.mokogit/workflows/branch-cleanup.yml
# VERSION: 01.00.00
# BRIEF: Delete feature branches after PR merge
@@ -30,13 +30,25 @@ jobs:
steps:
- name: Delete source branch
# SECURITY: the PR head ref is attacker-controllable. Pass it (and the
# repo/token) through env so the Actions engine cannot splice it into the
# shell source, and validate it to a safe charset before use.
env:
MOKOGIT_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
REPO: ${{ github.repository }}
API_BASE: ${{ vars.MOKOGIT_URL || 'https://git.mokoconsulting.tech' }}
BRANCH: ${{ github.event.pull_request.head.ref }}
run: |
BRANCH="${{ github.event.pull_request.head.ref }}"
API="${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}/api/v1/repos/${{ github.repository }}/branches"
ENCODED=$(php -r "echo rawurlencode('${BRANCH}');")
set -euo pipefail
if ! printf '%s' "${BRANCH}" | grep -Eq '^[A-Za-z0-9._/-]+$'; then
echo "::error::unsafe branch name; refusing to proceed"; exit 1
fi
API="${API_BASE}/api/v1/repos/${REPO}/branches"
# URL-encode the branch name's slashes (no PHP dependency on the runner)
ENCODED=$(printf '%s' "${BRANCH}" | sed 's|/|%2F|g')
STATUS=$(curl -sf -o /dev/null -w "%{http_code}" -X DELETE \
-H "Authorization: token ${{ secrets.MOKOGITEA_TOKEN }}" \
-H "Authorization: token ${MOKOGIT_TOKEN}" \
"${API}/${ENCODED}" 2>/dev/null || true)
if [ "$STATUS" = "204" ]; then
@@ -6,7 +6,7 @@
# DEFGROUP: MokoGIT.Workflow
# INGROUP: MokoStandards.CI
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic
# PATH: /.gitea/workflows/ci-generic.yml
# PATH: /.mokogit/workflows/ci-generic.yml
# VERSION: 01.00.00
# BRIEF: CI pipeline — lint, validate, and test for generic projects (PHP + Node.js)
@@ -4,11 +4,11 @@
#
# FILE INFORMATION
# DEFGROUP: MokoGIT.Workflow
# INGROUP: mokocli.Universal
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# PATH: /.mokogitea/workflows/ci-issue-reporter.yml
# INGROUP: MokoCLI.Universal
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic
# PATH: /.mokogit/workflows/ci-issue-reporter.yml
# VERSION: 01.00.00
# BRIEF: Reusable workflow creates/updates a MokoGIT issue when a CI gate fails.
# BRIEF: Reusable workflow creates/updates a MokoGIT issue when a CI gate fails.
# Clones MokoCLI and runs cli/ci_issue_reporter.sh.
name: "Universal: CI Issue Reporter"
@@ -35,7 +35,7 @@ on:
type: string
default: ""
secrets:
MOKOGITEA_TOKEN:
MOKOGIT_TOKEN:
required: true
env:
@@ -49,20 +49,27 @@ jobs:
steps:
- name: Clone MokoCLI
env:
MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
MOKOGIT_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
MOKOGIT_URL: ${{ vars.MOKOGIT_URL || 'https://git.mokoconsulting.tech' }}
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 "${MOKOGIT_URL}/MokoConsulting/mokocli.git" /tmp/mokocli
cd /tmp/mokocli && git sparse-checkout set cli/ci_issue_reporter.sh
- name: Report CI failure
env:
MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
MOKOGITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
MOKOGIT_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
MOKOGIT_URL: ${{ vars.MOKOGIT_URL || 'https://git.mokoconsulting.tech' }}
# Route all workflow_call inputs through env vars — referencing ${{ inputs.* }}
# directly in a run: block interpolates at YAML-parse time and is a shell-injection
# vector. Shell variables are not re-parsed, so a crafted input can't break out.
INPUT_GATE: ${{ inputs.gate }}
INPUT_DETAILS: ${{ inputs.details }}
INPUT_SEVERITY: ${{ inputs.severity }}
INPUT_WORKFLOW: ${{ inputs.workflow }}
run: |
chmod +x /tmp/mokocli/cli/ci_issue_reporter.sh
/tmp/mokocli/cli/ci_issue_reporter.sh \
--gate "${{ inputs.gate }}" \
--details "${{ inputs.details }}" \
--severity "${{ inputs.severity }}" \
--workflow "${{ inputs.workflow }}"
--gate "$INPUT_GATE" \
--details "$INPUT_DETAILS" \
--severity "$INPUT_SEVERITY" \
--workflow "$INPUT_WORKFLOW"
@@ -6,7 +6,7 @@
# DEFGROUP: MokoGIT.Workflow
# INGROUP: MokoStandards.Maintenance
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards
# PATH: /.gitea/workflows/cleanup.yml
# PATH: /.mokogit/workflows/cleanup.yml
# VERSION: 01.00.00
# BRIEF: Scheduled cleanup — delete merged branches and old workflow runs
@@ -21,7 +21,7 @@ permissions:
contents: write
env:
MOKOGITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
MOKOGIT_URL: ${{ vars.MOKOGIT_URL || 'https://git.mokoconsulting.tech' }}
jobs:
cleanup:
@@ -33,17 +33,17 @@ jobs:
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.MOKOGITEA_TOKEN }}
token: ${{ secrets.MOKOGIT_TOKEN }}
- name: Delete merged branches
env:
MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
MOKOGIT_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
run: |
echo "=== Merged Branch Cleanup ==="
API="${MOKOGITEA_URL}/api/v1/repos/${{ github.repository }}"
API="${MOKOGIT_URL}/api/v1/repos/${{ github.repository }}"
# List branches via API
BRANCHES=$(curl -sS -H "Authorization: token ${MOKOGITEA_TOKEN}" \
BRANCHES=$(curl -sS -H "Authorization: token ${MOKOGIT_TOKEN}" \
"${API}/branches?limit=50" | jq -r '.[].name')
DELETED=0
@@ -56,7 +56,7 @@ jobs:
# Check if branch is merged into main
if git merge-base --is-ancestor "origin/${BRANCH}" origin/main 2>/dev/null; then
echo " Deleting merged branch: ${BRANCH}"
curl -sS -X DELETE -H "Authorization: token ${MOKOGITEA_TOKEN}" \
curl -sS -X DELETE -H "Authorization: token ${MOKOGIT_TOKEN}" \
"${API}/branches/${BRANCH}" 2>/dev/null || true
DELETED=$((DELETED + 1))
fi
@@ -66,20 +66,20 @@ jobs:
- name: Clean old workflow runs
env:
MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
MOKOGIT_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
run: |
echo "=== Workflow Run Cleanup ==="
API="${MOKOGITEA_URL}/api/v1/repos/${{ github.repository }}"
API="${MOKOGIT_URL}/api/v1/repos/${{ github.repository }}"
CUTOFF=$(date -d "30 days ago" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -v-30d +%Y-%m-%dT%H:%M:%SZ)
# Get old completed runs
RUNS=$(curl -sS -H "Authorization: token ${MOKOGITEA_TOKEN}" \
RUNS=$(curl -sS -H "Authorization: token ${MOKOGIT_TOKEN}" \
"${API}/actions/runs?status=completed&limit=50" | \
jq -r ".workflow_runs[] | select(.created_at < \"${CUTOFF}\") | .id" 2>/dev/null)
DELETED=0
for RUN_ID in $RUNS; do
curl -sS -X DELETE -H "Authorization: token ${MOKOGITEA_TOKEN}" \
curl -sS -X DELETE -H "Authorization: token ${MOKOGIT_TOKEN}" \
"${API}/actions/runs/${RUN_ID}" 2>/dev/null || true
DELETED=$((DELETED + 1))
done
@@ -0,0 +1,83 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: MokoGIT.Workflow
# INGROUP: mokocli.Universal
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# PATH: /.mokogit/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: >-
startsWith(github.event.repository.name, 'Template-') &&
(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-NPM) PLATFORM="npm" ;;
Template-Generic) PLATFORM="" ;;
*) PLATFORM="" ;;
esac
echo "platform=$PLATFORM" >> "$GITHUB_OUTPUT"
echo "Platform: ${PLATFORM:-all}"
- name: Clone mokocli
env:
MOKOGIT_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
run: |
MOKOGIT_URL="${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}"
git clone --depth 1 "${MOKOGIT_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:
MOKOGIT_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
run: |
ARGS="--token ${MOKOGIT_TOKEN}"
ARGS="${ARGS} --org ${{ vars.GITEA_ORG || github.repository_owner }}"
ARGS="${ARGS} --phase repos"
ARGS="${ARGS} --delete-orphans"
PLATFORM="${{ steps.platform.outputs.platform }}"
if [ -n "$PLATFORM" ]; then
ARGS="${ARGS} --platform-filter ${PLATFORM}"
fi
php /tmp/mokocli/cli/workflow_sync.php ${ARGS}
@@ -18,7 +18,7 @@
# DEV_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)
# MOKOGIT_TOKEN - registry/API token (org secret)
name: Deploy (Dev)
@@ -64,7 +64,7 @@ jobs:
- name: Build and deploy to RC via SSH
env:
REGISTRY_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
REGISTRY_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
TAG: ${{ steps.config.outputs.tag }}
run: |
# Runner-side values (TAG, REGISTRY_TOKEN) are injected into the remote shell
@@ -18,7 +18,7 @@
# PROD_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)
# MOKOGIT_TOKEN - registry/API token (org secret)
name: Deploy (Prod)
@@ -64,7 +64,7 @@ jobs:
- name: Build and deploy to RC via SSH
env:
REGISTRY_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
REGISTRY_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
TAG: ${{ steps.config.outputs.tag }}
run: |
# Runner-side values (TAG, REGISTRY_TOKEN) are injected into the remote shell
@@ -18,7 +18,7 @@
# 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)
# MOKOGIT_TOKEN - registry/API token (org secret)
name: Deploy (RC)
@@ -66,7 +66,7 @@ jobs:
- name: Build and deploy to RC via SSH
env:
REGISTRY_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
REGISTRY_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
TAG: ${{ steps.config.outputs.tag }}
run: |
# Runner-side values (TAG, REGISTRY_TOKEN) are injected into the remote shell
@@ -34,7 +34,7 @@ permissions:
env:
NTFY_URL: ${{ vars.NTFY_URL || 'https://ntfy.mokoconsulting.tech' }}
NTFY_TOPIC: ${{ vars.NTFY_TOPIC || 'gitea-security' }}
NTFY_TOPIC: ${{ vars.NTFY_TOPIC || 'git-security' }}
jobs:
gitleaks:
@@ -4,7 +4,7 @@
#
# FILE INFORMATION
# DEFGROUP: MokoGIT.Workflow
# INGROUP: mokocli.Automation
# INGROUP: MokoCLI.Automation
# VERSION: 01.00.00
# BRIEF: Auto-create feature branch when an issue is opened
@@ -19,7 +19,7 @@ permissions:
issues: write
env:
MOKOGITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
MOKOGIT_URL: ${{ vars.MOKOGIT_URL || 'https://git.mokoconsulting.tech' }}
jobs:
create-branch:
@@ -27,14 +27,22 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Create branch and comment
# SECURITY: never interpolate github.event.* into a run: script — the
# Actions engine splices the raw value into the shell source (command
# injection via a crafted issue title). Pass everything through env so
# the values arrive as ordinary shell variables that are not re-parsed.
env:
MOKOGIT_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
REPO: ${{ github.repository }}
ISSUE_NUM: ${{ github.event.issue.number }}
ISSUE_TITLE: ${{ github.event.issue.title }}
run: |
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
API="${MOKOGITEA_URL}/api/v1/repos/${{ github.repository }}"
ISSUE_NUM="${{ github.event.issue.number }}"
ISSUE_TITLE="${{ github.event.issue.title }}"
TOKEN="${MOKOGIT_TOKEN}"
API="${MOKOGIT_URL}/api/v1/repos/${REPO}"
# Build slug from title: lowercase, replace non-alnum with dash, trim
SLUG=$(echo "${ISSUE_TITLE}" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/--*/-/g' | sed 's/^-//;s/-$//' | cut -c1-40)
# Build slug from title: lowercase, replace non-alnum with dash, trim.
# printf (not echo) so a title beginning with "-" is not read as flags.
SLUG=$(printf '%s' "${ISSUE_TITLE}" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/--*/-/g' | sed 's/^-//;s/-$//' | cut -c1-40)
BRANCH="feature/${ISSUE_NUM}-${SLUG}"
# Check dev branch exists
@@ -58,7 +66,7 @@ jobs:
echo "Created branch: ${BRANCH}"
# Comment on issue with branch link
REPO_URL="${MOKOGITEA_URL}/${{ github.repository }}"
REPO_URL="${MOKOGIT_URL}/${REPO}"
BODY="Branch created: [\`${BRANCH}\`](${REPO_URL}/src/branch/${BRANCH})\n\n\`\`\`bash\ngit fetch origin\ngit checkout ${BRANCH}\n\`\`\`"
curl -sf -X POST \
@@ -6,7 +6,7 @@
# DEFGROUP: MokoGIT.Workflow
# INGROUP: MokoStandards.Notifications
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards
# PATH: /.gitea/workflows/notify.yml
# PATH: /.mokogit/workflows/notify.yml
# VERSION: 01.00.00
# BRIEF: Push notifications via ntfy on release success or workflow failure
@@ -26,7 +26,7 @@ permissions:
env:
NTFY_URL: ${{ vars.NTFY_URL || 'https://ntfy.mokoconsulting.tech' }}
NTFY_TOPIC: ${{ vars.NTFY_TOPIC || 'gitea-releases' }}
NTFY_TOPIC: ${{ vars.NTFY_TOPIC || 'git-releases' }}
jobs:
notify:
@@ -497,12 +497,12 @@ jobs:
steps:
- name: Trigger RC pre-release
env:
MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
MOKOGIT_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
REPO: ${{ github.repository }}
BRANCH: ${{ github.head_ref }}
MOKOGITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
MOKOGIT_URL: ${{ vars.MOKOGIT_URL || 'https://git.mokoconsulting.tech' }}
run: |
curl -s -X POST "${MOKOGITEA_URL}/api/v1/repos/${REPO}/actions/workflows/pre-release.yml/dispatches" -H "Authorization: token ${MOKOGITEA_TOKEN}" -H "Content-Type: application/json" -d "{\"ref\":\"${BRANCH}\",\"inputs\":{\"stability\":\"release-candidate\"}}"
curl -s -X POST "${MOKOGIT_URL}/api/v1/repos/${REPO}/actions/workflows/pre-release.yml/dispatches" -H "Authorization: token ${MOKOGIT_TOKEN}" -H "Content-Type: application/json" -d "{\"ref\":\"${BRANCH}\",\"inputs\":{\"stability\":\"release-candidate\"}}"
echo "### Pre-Release" >> $GITHUB_STEP_SUMMARY
echo "Triggered RC build on branch \`${BRANCH}\`" >> $GITHUB_STEP_SUMMARY
@@ -513,7 +513,7 @@ jobs:
if: >-
always() &&
needs.validate.result == 'failure'
uses: ./.mokogitea/workflows/ci-issue-reporter.yml
uses: ./.mokogit/workflows/ci-issue-reporter.yml
with:
gate: "PR Validation"
workflow: "PR Check"
@@ -40,9 +40,9 @@ permissions:
contents: write
env:
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
GITEA_ORG: ${{ vars.GITEA_ORG || github.repository_owner }}
GITEA_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }}
GIT_URL: ${{ vars.MOKOGIT_URL || 'https://git.mokoconsulting.tech' }}
GIT_ORG: ${{ vars.MOKOGIT_ORG || github.repository_owner }}
GIT_REPO: ${{ vars.MOKOGIT_REPO || github.event.repository.name }}
jobs:
build:
@@ -57,7 +57,7 @@ jobs:
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.MOKOGITEA_TOKEN }}
token: ${{ secrets.MOKOGIT_TOKEN }}
ref: ${{ github.ref_name }}
submodules: recursive
@@ -67,7 +67,7 @@ jobs:
- name: Setup mokocli tools
env:
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
run: |
# Use pre-installed /opt/mokocli if available (updated by cron every 6h)
@@ -154,7 +154,7 @@ jobs:
# Commit version bump
git config --local user.email "mokogit-actions[bot]@mokoconsulting.tech"
git config --local user.name "mokogit-actions[bot]"
git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
git remote set-url origin "https://x-access-token:${{ secrets.MOKOGIT_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]"
@@ -164,12 +164,12 @@ jobs:
# Auto-detect element via manifest_element.php
php ${MOKO_CLI}/manifest_element.php \
--path . --version "$VERSION" --stability "$STABILITY" \
--repo "${GITEA_REPO}" --github-output
--repo "${GIT_REPO}" --github-output
# Read back element outputs
EXT_ELEMENT=$(grep '^ext_element=' "$GITHUB_OUTPUT" | tail -1 | cut -d= -f2)
ZIP_NAME=$(grep '^zip_name=' "$GITHUB_OUTPUT" | tail -1 | cut -d= -f2)
[ -z "$EXT_ELEMENT" ] && EXT_ELEMENT=$(echo "${GITEA_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -')
[ -z "$EXT_ELEMENT" ] && EXT_ELEMENT=$(echo "${GIT_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -')
[ -z "$ZIP_NAME" ] && ZIP_NAME="${EXT_ELEMENT}-${VERSION}.zip"
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
@@ -187,18 +187,18 @@ jobs:
run: |
TAG="${{ steps.meta.outputs.tag }}"
VERSION="${{ steps.meta.outputs.version }}"
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
API_BASE="${GIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}"
php ${MOKO_CLI}/release_create.php \
--path . --version "$VERSION" --tag "$TAG" \
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
--repo "${GITEA_REPO}" --branch "${{ github.ref_name }}" --prerelease
--token "${{ secrets.MOKOGIT_TOKEN }}" --api-base "$API_BASE" \
--repo "${GIT_REPO}" --branch "${{ github.ref_name }}" --prerelease
- name: Update release notes from CHANGELOG.md
if: steps.eligibility.outputs.proceed == 'true'
run: |
TAG="${{ steps.meta.outputs.tag }}"
VERSION="${{ steps.meta.outputs.version }}"
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
API_BASE="${GIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}"
# Extract [Unreleased] section from changelog (everything between [Unreleased] and next ## heading)
if [ -f "CHANGELOG.md" ]; then
@@ -209,7 +209,7 @@ jobs:
fi
# Update release body via API
RELEASE_ID=$(curl -sf -H "Authorization: token ${{ secrets.MOKOGITEA_TOKEN }}" \
RELEASE_ID=$(curl -sf -H "Authorization: token ${{ secrets.MOKOGIT_TOKEN }}" \
"${API_BASE}/releases/tags/${TAG}" | python3 -c "import json,sys; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true)
if [ -n "$RELEASE_ID" ]; then
@@ -221,7 +221,7 @@ jobs:
'${API_BASE}/releases/${RELEASE_ID}',
data=payload, method='PATCH',
headers={
'Authorization': 'token ${{ secrets.MOKOGITEA_TOKEN }}',
'Authorization': 'token ${{ secrets.MOKOGIT_TOKEN }}',
'Content-Type': 'application/json'
})
urllib.request.urlopen(req)
@@ -235,11 +235,11 @@ jobs:
run: |
VERSION="${{ steps.meta.outputs.version }}"
TAG="${{ steps.meta.outputs.tag }}"
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
API_BASE="${GIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}"
php ${MOKO_CLI}/release_package.php \
--path . --version "$VERSION" --tag "$TAG" \
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
--repo "${GITEA_REPO}" --output /tmp || true
--token "${{ secrets.MOKOGIT_TOKEN }}" --api-base "$API_BASE" \
--repo "${GIT_REPO}" --output /tmp || true
# updates.xml is generated dynamically by MokoGIT license server
# No need to build, commit, or sync updates.xml from workflows
@@ -248,8 +248,8 @@ jobs:
if: steps.eligibility.outputs.proceed == 'true'
continue-on-error: true
run: |
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
API_BASE="${GIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}"
TOKEN="${{ secrets.MOKOGIT_TOKEN }}"
php ${MOKO_CLI}/release_cascade.php \
--stability "${{ steps.meta.outputs.stability }}" \
@@ -6,7 +6,7 @@
# DEFGROUP: MokoGIT.Workflow
# INGROUP: mokocli.Universal
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# PATH: /.mokogitea/workflows/rc-revert.yml
# PATH: /.mokogit/workflows/rc-revert.yml
# VERSION: 09.23.00
# BRIEF: Rename rc/ branch back to dev/ when PR is closed without merge
@@ -32,8 +32,8 @@ jobs:
env:
BRANCH: ${{ github.event.pull_request.head.ref }}
REPO: ${{ github.repository }}
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
GIT_URL: ${{ vars.MOKOGIT_URL || 'https://git.mokoconsulting.tech' }}
TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
run: |
set -euo pipefail
# BRANCH is attacker-controlled (PR head ref). Strict allowlist before ANY use.
@@ -42,7 +42,7 @@ jobs:
fi
SUFFIX="${BRANCH#rc/}"
DEV_BRANCH="dev/${SUFFIX}"
API="${GITEA_URL}/api/v1/repos/${REPO}/branches"
API="${GIT_URL}/api/v1/repos/${REPO}/branches"
# Create dev/ branch from rc/ branch
STATUS=$(curl -sf -o /dev/null -w "%{http_code}" -X POST \
@@ -45,7 +45,7 @@ env:
SCRIPTS_ALLOWED_DIRS: scripts,scripts/fix,scripts/lib,scripts/release,scripts/run,scripts/validate
# Repo health policy
REPO_REQUIRED_ARTIFACTS: README.md,LICENSE,CHANGELOG.md,CONTRIBUTING.md,CODE_OF_CONDUCT.md,.mokogitea/workflows/
REPO_REQUIRED_ARTIFACTS: README.md,LICENSE,CHANGELOG.md,CONTRIBUTING.md,CODE_OF_CONDUCT.md,.mokogit/workflows/
REPO_OPTIONAL_FILES: SECURITY.md,GOVERNANCE.md,.editorconfig,.gitattributes,.gitignore,README.md,docs/
REPO_DISALLOWED_DIRS:
REPO_DISALLOWED_FILES: TODO.md,todo.md
@@ -56,7 +56,7 @@ env:
# File / directory variables
DOCS_INDEX: docs/docs-index.md
SCRIPT_DIR: scripts
WORKFLOWS_DIR: .mokogitea/workflows
WORKFLOWS_DIR: .mokogit/workflows
SHELLCHECK_PATTERN: '*.sh'
SPDX_FILE_GLOBS: '*.sh,*.php,*.js,*.ts,*.css,*.xml,*.yml,*.yaml'
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
@@ -77,7 +77,7 @@ jobs:
- name: Check actor permission (admin only)
id: perm
env:
TOKEN: ${{ secrets.MOKOGITEA_TOKEN || github.token }}
TOKEN: ${{ secrets.MOKOGIT_TOKEN || github.token }}
REPO: ${{ github.repository }}
ACTOR: ${{ github.actor }}
run: |
@@ -95,7 +95,7 @@ jobs:
;;
*)
# Detect platform and check permissions via API
API_BASE="${GITHUB_API_URL:-${GITEA_API_URL:-https://api.github.com}}"
API_BASE="${GITHUB_API_URL:-${GIT_API_URL:-https://api.github.com}}"
RESP=$(curl -sf -H "Authorization: token ${TOKEN}" \
"${API_BASE}/repos/${REPO}/collaborators/${ACTOR}/permission" 2>/dev/null || echo '{}')
PERMISSION=$(echo "$RESP" | grep -oP '"permission"\s*:\s*"\K[^"]+' || echo "unknown")
@@ -677,7 +677,7 @@ jobs:
if: >-
always() &&
needs.scripts_governance.result == 'failure'
uses: ./.mokogitea/workflows/ci-issue-reporter.yml
uses: ./.mokogit/workflows/ci-issue-reporter.yml
with:
gate: "Scripts Governance"
workflow: "Repo Health"
@@ -691,7 +691,7 @@ jobs:
if: >-
always() &&
needs.repo_health.result == 'failure'
uses: ./.mokogitea/workflows/ci-issue-reporter.yml
uses: ./.mokogit/workflows/ci-issue-reporter.yml
with:
gate: "Repository Health"
workflow: "Repo Health"
@@ -4,7 +4,7 @@
# DEFGROUP: GitHub.Workflow
# INGROUP: MokoCLI.Compliance
# REPO: https://github.com/mokoconsulting-tech/mokocli
# PATH: /.mokogitea/workflows/standards-compliance.yml
# PATH: /.mokogit/workflows/standards-compliance.yml
# VERSION: 04.06.00
# BRIEF: MokoCLI compliance validation workflow
# NOTE: Validates repository structure, documentation, and coding standards
@@ -320,7 +320,7 @@ jobs:
echo "### 🔴 Critical Issues: $MISSING" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Remediation Steps:**" >> $GITHUB_STEP_SUMMARY
[ ! -d ".mokogit" ] && echo "- Create .mokogit directory: \`mkdir -p .mokogitea/workflows\`" >> $GITHUB_STEP_SUMMARY
[ ! -d ".mokogit" ] && echo "- Create .mokogit directory: \`mkdir -p .mokogit/workflows\`" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "📚 Reference: [MokoCLI Repository Structure](https://github.com/mokoconsulting-tech/mokocli/tree/main/docs/policy/core-structure.md)" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
@@ -563,17 +563,17 @@ jobs:
set -x
echo "### GitHub Actions Workflows" >> $GITHUB_STEP_SUMMARY
WORKFLOWS_DIR=".mokogitea/workflows"
WORKFLOWS_DIR=".mokogit/workflows"
if [ ! -d "$WORKFLOWS_DIR" ]; then
echo "❌ No workflows directory found" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### ❌ Validation Failed: Workflows Directory Missing" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Error:** .mokogitea/workflows directory is required for CI/CD automation" >> $GITHUB_STEP_SUMMARY
echo "**Action Required:** Create .mokogitea/workflows directory and add GitHub Actions workflows" >> $GITHUB_STEP_SUMMARY
echo "**Error:** .mokogit/workflows directory is required for CI/CD automation" >> $GITHUB_STEP_SUMMARY
echo "**Action Required:** Create .mokogit/workflows directory and add GitHub Actions workflows" >> $GITHUB_STEP_SUMMARY
echo ""
echo "❌ ERROR: .mokogitea/workflows directory not found"
echo "❌ ERROR: .mokogit/workflows directory not found"
exit 1
fi
@@ -612,7 +612,7 @@ jobs:
echo "### Workflow YAML Syntax" >> $GITHUB_STEP_SUMMARY
INVALID=0
for workflow in $(find .mokogitea/workflows -maxdepth 1 -type f \( -name "*.yml" -o -name "*.yaml" \) 2>/dev/null); do
for workflow in $(find .mokogit/workflows -maxdepth 1 -type f \( -name "*.yml" -o -name "*.yaml" \) 2>/dev/null); do
if [ -f "$workflow" ]; then
if python3 -c "import yaml, sys; yaml.safe_load(open(sys.argv[1]))" "$workflow" 2>/dev/null; then
echo "✅ $(basename $workflow)" >> $GITHUB_STEP_SUMMARY
@@ -629,7 +629,7 @@ jobs:
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Error:** $INVALID workflow file(s) have invalid YAML syntax" >> $GITHUB_STEP_SUMMARY
echo "**Action Required:** Fix YAML syntax errors in the marked workflow files" >> $GITHUB_STEP_SUMMARY
echo "**Tool:** Run \`python3 -c \"import yaml; yaml.safe_load(open('.mokogitea/workflows/FILE.yml'))\"\` locally" >> $GITHUB_STEP_SUMMARY
echo "**Tool:** Run \`python3 -c \"import yaml; yaml.safe_load(open('.mokogit/workflows/FILE.yml'))\"\` locally" >> $GITHUB_STEP_SUMMARY
echo ""
echo "❌ ERROR: $INVALID workflow file(s) with invalid YAML syntax"
exit 1
@@ -641,7 +641,7 @@ jobs:
echo "✅ SUCCESS: All workflow files passed YAML validation"
- name: Validate CodeQL Configuration
if: hashFiles('.mokogitea/workflows/codeql-analysis.yml') != ''
if: hashFiles('.mokogit/workflows/codeql-analysis.yml') != ''
run: |
set -e
echo "" >> $GITHUB_STEP_SUMMARY
@@ -649,7 +649,7 @@ jobs:
echo "" >> $GITHUB_STEP_SUMMARY
# Inline validation (rewritten from Python to bash for PHP-only architecture)
CODEQL_FILE=".mokogitea/workflows/codeql-analysis.yml"
CODEQL_FILE=".mokogit/workflows/codeql-analysis.yml"
if [ ! -f "$CODEQL_FILE" ]; then
echo "⚠️ CodeQL workflow file not found" >> $GITHUB_STEP_SUMMARY
@@ -1186,7 +1186,7 @@ jobs:
fi
# Run yamllint and count line-length warnings
YAML_OUTPUT=$(yamllint .mokogitea/workflows/*.yml 2>&1 | grep "line too long" || true)
YAML_OUTPUT=$(yamllint .mokogit/workflows/*.yml 2>&1 | grep "line too long" || true)
if [ -n "$YAML_OUTPUT" ]; then
YAML_VIOLATIONS=$(echo "$YAML_OUTPUT" | wc -l)
echo "⚠️ Found $YAML_VIOLATIONS lines exceeding 180 characters in YAML files" >> $GITHUB_STEP_SUMMARY
@@ -6,7 +6,7 @@
# DEFGROUP: MokoGIT.Workflow.Template
# INGROUP: MokoStandards.CI
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Joomla
# PATH: /.mokogitea/workflows/version-set.yml
# PATH: /.mokogit/workflows/version-set.yml
# VERSION: 01.00.00
# BRIEF: Set or reset the extension version across all version-bearing files
@@ -48,7 +48,7 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
with:
token: ${{ secrets.MOKOGITEA_TOKEN || github.token }}
token: ${{ secrets.MOKOGIT_TOKEN || github.token }}
ref: ${{ inputs.branch || github.ref }}
fetch-depth: 1
@@ -1,10 +0,0 @@
# DISABLED — auto-release Step 11 recreates dev from main after every release.
# Cascade-dev is redundant and causes version conflicts when both main and dev
# have different version numbers in templateDetails.xml / manifest.xml.
name: "Cascade Main → Dev (DISABLED)"
on: workflow_dispatch
jobs:
noop:
runs-on: ubuntu-latest
steps:
- run: echo "Cascade disabled — auto-release handles dev recreation"
@@ -1,179 +0,0 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
# SPDX-License-Identifier: GPL-3.0-or-later
# BRIEF: Auto-build RC release on PR to main, update RC update stream
name: "PR RC Release"
on:
pull_request:
types: [opened, synchronize]
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
REGISTRY: code.mokoconsulting.tech
IMAGE: mokoconsulting/mokogit
permissions:
contents: write
jobs:
rc-release:
name: Build RC Release
runs-on: ubuntu-latest
steps:
- name: Check target branch
id: guard
env:
BASE_BRANCH: ${{ github.base_ref }}
run: |
echo "PR target: ${BASE_BRANCH}"
if [ "$BASE_BRANCH" != "main" ]; then
echo "skip=true" >> "$GITHUB_OUTPUT"
echo "Skipping RC — only for PRs targeting main"
else
echo "skip=false" >> "$GITHUB_OUTPUT"
fi
- name: Checkout PR branch
if: steps.guard.outputs.skip != 'true'
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: 0
- name: Determine RC version
if: steps.guard.outputs.skip != 'true'
id: version
env:
PR_NUMBER: ${{ github.event.pull_request.number }}
run: |
# This RC flow drives a Joomla-style update stream (updates.xml). Repos that don't ship
# one (e.g. generic Go/TS) have nothing to package here, so no-op cleanly instead of
# aborting under `set -e` when the file is absent.
if [ ! -f updates.xml ]; then
echo "has_updates=false" >> "$GITHUB_OUTPUT"
echo "No updates.xml in this repo — skipping RC update-stream packaging"
exit 0
fi
echo "has_updates=true" >> "$GITHUB_OUTPUT"
BASE_VERSION=$(sed -n 's/.*<version>\(.*\)<\/version>.*/\1/p' updates.xml | head -1)
[ -z "$BASE_VERSION" ] && BASE_VERSION="04.00.00"
RC_VERSION="${BASE_VERSION}-rc.${PR_NUMBER}"
RC_TAG="v1.26.1-moko.${RC_VERSION}"
echo "version=$RC_VERSION" >> "$GITHUB_OUTPUT"
echo "tag=$RC_TAG" >> "$GITHUB_OUTPUT"
echo "RC version: $RC_VERSION (tag: $RC_TAG)"
- name: Update updates.xml RC channel
if: steps.guard.outputs.skip != 'true' && steps.version.outputs.has_updates == 'true'
env:
RC_VERSION: ${{ steps.version.outputs.version }}
RC_TAG: ${{ steps.version.outputs.tag }}
PR_URL: ${{ github.event.pull_request.html_url }}
PR_NUM: ${{ github.event.pull_request.number }}
run: |
DOCKER_TAG="${REGISTRY}/${IMAGE}:${RC_TAG}"
python3 << 'PYEOF'
import os, re
rc_version = os.environ["RC_VERSION"]
rc_tag = os.environ["RC_TAG"]
pr_url = os.environ["PR_URL"]
pr_num = os.environ["PR_NUM"]
docker_tag = os.environ["REGISTRY"] + "/" + os.environ["IMAGE"] + ":" + rc_tag
entry = f""" <update>
<name>MokoGIT</name>
<description>MokoGIT RC from PR #{pr_num}</description>
<element>mokogit</element>
<type>application</type>
<version>{rc_version}</version>
<client>server</client>
<tags><tag>rc</tag></tags>
<infourl title="MokoGIT RC">{pr_url}</infourl>
<downloads>
<downloadurl type="full" format="docker">{docker_tag}</downloadurl>
</downloads>
<sha256></sha256>
<targetplatform name="mokogit" version="((1\\.25\\.)|(1\\.26\\.))" />
<maintainer>Moko Consulting</maintainer>
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
</update>"""
content = open("updates.xml").read()
# Remove existing RC entry
content = re.sub(
r"\s*<update>[\s\S]*?<tag>rc</tag>[\s\S]*?</update>",
"",
content,
)
# Insert before </updates>
content = content.replace("</updates>", entry + "\n</updates>")
open("updates.xml", "w").write(content)
print(f"Updated updates.xml with RC entry: {rc_version}")
PYEOF
- name: Create RC release
if: steps.guard.outputs.skip != 'true' && steps.version.outputs.has_updates == 'true'
env:
GITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
RC_TAG: ${{ steps.version.outputs.tag }}
RC_VERSION: ${{ steps.version.outputs.version }}
PR_TITLE: ${{ github.event.pull_request.title }}
PR_URL: ${{ github.event.pull_request.html_url }}
PR_NUMBER: ${{ github.event.pull_request.number }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
API_BASE: https://${{ env.REGISTRY }}/api/v1/repos/${{ github.repository }}
run: |
# Delete existing RC release/tag if present
curl -s -X DELETE -H "Authorization: token ${GITEA_TOKEN}" \
"${API_BASE}/releases/tags/${RC_TAG}" 2>/dev/null || true
curl -s -X DELETE -H "Authorization: token ${GITEA_TOKEN}" \
"${API_BASE}/tags/${RC_TAG}" 2>/dev/null || true
# Create prerelease
python3 << PYEOF
import json, os, urllib.request
api = os.environ["API_BASE"]
token = os.environ["GITEA_TOKEN"]
payload = json.dumps({
"tag_name": os.environ["RC_TAG"],
"target_commitish": os.environ["HEAD_SHA"],
"name": f"RC: {os.environ['PR_TITLE']}",
"body": f"Release candidate from PR #{os.environ['PR_NUMBER']}\n\nPR: {os.environ['PR_URL']}\nDocker: docker pull {os.environ['REGISTRY']}/{os.environ['IMAGE']}:{os.environ['RC_TAG']}",
"draft": False,
"prerelease": True,
}).encode()
req = urllib.request.Request(
f"{api}/releases",
data=payload,
headers={
"Authorization": f"token {token}",
"Content-Type": "application/json",
},
method="POST",
)
with urllib.request.urlopen(req) as resp:
result = json.loads(resp.read())
print(f"Created RC release: {result.get('tag_name')}")
PYEOF
- name: Commit updates.xml
if: steps.guard.outputs.skip != 'true' && steps.version.outputs.has_updates == 'true'
env:
GITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
HEAD_REF: ${{ github.event.pull_request.head.ref }}
PR_NUM: ${{ github.event.pull_request.number }}
run: |
git config user.name "MokoGIT Bot"
git config user.email "deploy@mokoconsulting.tech"
git add updates.xml
if git diff --cached --quiet; then
echo "No changes to updates.xml"
else
git commit -m "chore(ci): update RC stream for PR #${PR_NUM}"
git push origin "HEAD:${HEAD_REF}" || echo "Push failed"
fi
@@ -1,12 +0,0 @@
# Test workflow to verify .mokogit/ directory is discovered
name: Test .mokogit workflows
on:
workflow_dispatch:
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Verify .mokogit
run: echo "This workflow ran from .mokogitea/workflows/ — feature works!"
@@ -1,167 +0,0 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
# SPDX-License-Identifier: GPL-3.0-or-later
# BRIEF: Sync upstream Gitea bug fixes into MokoGIT issue tracker
name: Upstream Bug Sync
on:
schedule:
- cron: '0 8 * * *' # daily at 08:00 UTC
workflow_dispatch:
inputs:
days_back:
description: 'How many days back to scan (default: 7)'
required: false
default: '7'
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs:
sync:
runs-on: ubuntu-latest
steps:
- name: Sync upstream bugs
env:
GH_TOKEN: ${{ secrets.GH_MIRROR_TOKEN }}
MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
MOKOGITEA_URL: https://code.mokoconsulting.tech
MOKOGITEA_REPO: MokoConsulting/MokoGIT
UPSTREAM_BRANCH: release/v1.26
DAYS_BACK: ${{ github.event.inputs.days_back || '7' }}
run: |
python3 << 'PYEOF'
import json, os, re, sys, urllib.parse, urllib.request
from datetime import datetime, timedelta, timezone
GH_TOKEN = os.environ["GH_TOKEN"]
MOKO_TOKEN = os.environ["MOKOGITEA_TOKEN"]
MOKO_URL = os.environ["MOKOGITEA_URL"]
MOKO_REPO = os.environ["MOKOGITEA_REPO"]
BRANCH = os.environ["UPSTREAM_BRANCH"]
DAYS = int(os.environ.get("DAYS_BACK", "7"))
# Label IDs in MokoGIT
LABELS = {
"type_bug": 5757, "upstream": 5758, "security": 5032,
"critical": 5018, "high": 5019, "medium": 5020, "low": 5021,
}
def gh_get(url):
req = urllib.request.Request(url, headers={
"Authorization": f"token {GH_TOKEN}",
"Accept": "application/vnd.github.v3+json",
})
with urllib.request.urlopen(req) as r:
return json.loads(r.read())
def moko_get(path):
req = urllib.request.Request(f"{MOKO_URL}/api/v1/{path}", headers={
"Authorization": f"token {MOKO_TOKEN}",
})
with urllib.request.urlopen(req) as r:
return json.loads(r.read())
def moko_post(path, data):
payload = json.dumps(data).encode()
req = urllib.request.Request(f"{MOKO_URL}/api/v1/{path}",
data=payload, method="POST", headers={
"Authorization": f"token {MOKO_TOKEN}",
"Content-Type": "application/json",
})
with urllib.request.urlopen(req) as r:
return json.loads(r.read())
# ── Step 1: Find recently merged upstream PRs ──
since = (datetime.now(timezone.utc) - timedelta(days=DAYS)).strftime("%Y-%m-%dT%H:%M:%SZ")
query = f"repo:go-gitea/gitea is:pr is:merged base:{BRANCH} merged:>={since}"
encoded = urllib.parse.quote(query)
print(f"Scanning: {query}")
result = gh_get(f"https://api.github.com/search/issues?q={encoded}&per_page=100&sort=updated&order=desc")
total = result["total_count"]
print(f"Found {total} merged PRs in the last {DAYS} days")
if total == 0:
print("Nothing to sync.")
sys.exit(0)
# ── Step 2: Filter for bug/security fixes ──
bugs = []
for pr in result["items"]:
title = pr["title"]
label_names = [l["name"].lower() for l in pr.get("labels", [])]
is_fix = title.lower().startswith("fix")
is_security = any("security" in l for l in label_names) or "[security]" in title.lower()
is_bug = any("bug" in l for l in label_names)
if not (is_fix or is_security or is_bug):
continue
refs = re.findall(r"#(\d+)", title)
severity = "critical" if is_security and "[security]" in title.lower() else \
"high" if is_security else "medium"
bugs.append({
"number": pr["number"], "title": title, "url": pr["html_url"],
"severity": severity, "is_security": is_security, "refs": refs,
"merged": pr.get("pull_request", {}).get("merged_at", "")[:10],
})
print(f"Filtered to {len(bugs)} bug/security fixes")
if not bugs:
sys.exit(0)
# ── Step 3: Collect already-tracked PR numbers ──
tracked = set()
for state in ["open", "closed"]:
try:
issues = moko_get(f"repos/{MOKO_REPO}/issues?state={state}&type=issues&limit=50&labels=upstream")
for iss in issues:
text = (iss.get("body") or "") + " " + (iss.get("title") or "")
tracked.update(re.findall(r"(?:#|/pull/)(\d{4,})", text))
except Exception:
pass
print(f"Already tracked: {len(tracked)} upstream PRs")
# ── Step 4: Create issues for new bugs ──
created = skipped = errors = 0
for bug in bugs:
if any(r in tracked for r in bug["refs"]):
print(f" SKIP #{bug['number']}: {bug['title'][:55]} (tracked)")
skipped += 1
continue
labels = [LABELS["type_bug"], LABELS["upstream"], LABELS[bug["severity"]]]
if bug["is_security"]:
labels.append(LABELS["security"])
body = (
f"## Summary\n\n"
f"Upstream bug fix merged into `{BRANCH}`.\n\n"
f"## Upstream Reference\n\n"
f"- PR: {bug['url']}\n"
f"- Merged: {bug['merged']}\n"
f"- Branch: {BRANCH}\n\n"
f"## Severity: {bug['severity'].title()}"
f"{' (security)' if bug['is_security'] else ''}\n\n"
f"## Action\n\n"
f"Cherry-pick from upstream `{BRANCH}` branch.\n\n"
f"---\n"
f"*Auto-created by upstream-bug-sync workflow*\n"
f"*Authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>*"
)
try:
iss = moko_post(f"repos/{MOKO_REPO}/issues", {
"title": bug["title"], "body": body, "labels": labels,
})
print(f" CREATED #{iss['number']}: {bug['title'][:55]}")
created += 1
except Exception as e:
print(f" ERROR #{bug['number']}: {e}")
errors += 1
print(f"\n=== Done: {created} created, {skipped} skipped, {errors} errors ===")
PYEOF
+8
View File
@@ -2,10 +2,18 @@
## [Unreleased]
### Fixed
- **Actions: `.mokogit/workflows` custom-path detection restored** — `WorkflowDirs` now scans `.mokogit/workflows`; the legacy `.mokogitea/workflows` is fully retired. Workflow indexing, push-triggered CI/deploys, and `workflow_dispatch` work again (#798)
- **Issue custom-status dropdown shows its options again** — the status `<select>` carried the `ui compact dropdown` class, so fomantic turned it into an overlay menu that painted behind the page content (options were present in the DOM but invisible); it is now a plain native `<select>` with its inline styles moved to a stylesheet (`web_src/css/repo/issue-status.css`)
- Restored the repo's `.mokogit/workflows/` (deploy + org CI, ~20 workflows) from Template-Go after the workflow sync had dropped them
### Changed
- Adopt MokoOrgStandards `xx.xx.xx` versioning: the app version now comes from a committed `VERSION` file (`01.00.00`), so the binary reports the MokoGIT version instead of the leaked upstream Gitea `1.24.0+dev-N-ghash`; the upstream update-checker is disabled by default
- **Rebrand: MokoGitea → MokoGIT (complete retirement of the MokoGitea name).** Go module path `code.mokoconsulting.tech/MokoConsulting/MokoGitea``.../MokoGIT` (all imports + `go.mod`); default app name `MokoGitea``MokoGIT`; the in-repo config directory and the special org profile/wiki repo names `.mokogitea` / `.mokogitea-private``.mokogit` / `.mokogit-private`; the Actions system user `mokogitea-actions``mokogit-actions` (DB migration #369); Docker image, ntfy topic, mail tags, and remaining lowercase `mokogitea` references → `mokogit`; bundled the Moko `favicon.svg` as the app icon and PWA manifest icon. Shared `MOKOGITEA_*` CI/compose env-var and org-secret names are renamed in the coordinated server cutover (not in this repo change) to avoid breaking cross-repo CI mid-transition.
### Added
- **Org internal wiki merge** — the organization Overview renders the org's internal wiki landing page (from the `.mokogit` public / `.mokogit-private` members-only repo wiki, resolved `home``index``readme``profile`) instead of a separate profile README, with a safe fallback to the legacy README; the per-repo Wiki tab is hidden for the `.mokogit` / `.mokogit-private` profile repos since their wiki now surfaces as the Overview; the org-settings "Internal wiki" control is relabeled to `.mokogit` / `.mokogit-private`
- Staged `.vault` git-stack deploy compose under `deploy/git/{prod,rc,dev}/` (per-tier `docker-compose.yml` + `VERSION` + `.env.example`; every secret externalized as `${VAR}`), ready to drop into the `.vault` hub
- 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)
+11
View File
@@ -37,6 +37,17 @@ See the [org wiki](https://git.mokoconsulting.tech/MokoConsulting/.mokogit/wiki/
This project is licensed under the GNU General Public License v3.0 or later -- see the [LICENSE](LICENSE) file.
## Third-Party & Vendored Assets
MokoGIT bundles third-party assets under their own licenses. Full attribution and
license texts are in [THIRD-PARTY-NOTICES.md](THIRD-PARTY-NOTICES.md) and travel with
every distribution and white-labeled build.
- **[Font Awesome Free 7.1.0](https://fontawesome.com)** -- (c) 2025 Fonticons, Inc.
Icons under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/), fonts under
[SIL OFL 1.1](https://scripts.sil.org/OFL), code under [MIT](https://opensource.org/licenses/MIT).
Vendored unmodified at `web_src/css/vendor/fontawesome/` (see its `LICENSE.txt`).
---
*[Moko Consulting](https://mokoconsulting.tech)*
+40
View File
@@ -0,0 +1,40 @@
<!--
SPDX-License-Identifier: MIT
This file lists third-party assets vendored into MokoGIT and the attribution
required by their licenses. It is intended to travel with every distribution and
white-labeled build. Do not remove third-party attribution when rebranding.
-->
# Third-Party Notices
MokoGIT bundles the third-party assets listed below. Each is redistributed under
its own license; the required attribution and license texts are retained here and
alongside the vendored files. Rebranding / white-labeling MokoGIT does **not**
remove these obligations — this file (and the referenced license files) must be
distributed with the product.
## Font Awesome Free 7.1.0
- **Project:** Font Awesome Free
- **Version:** 7.1.0
- **Author / Copyright:** © 2025 Fonticons, Inc. — https://fontawesome.com
- **Homepage / License:** https://fontawesome.com/license/free
- **Vendored at:** `web_src/css/vendor/fontawesome/` (CSS + webfonts, unmodified)
- **Full license text:** `web_src/css/vendor/fontawesome/LICENSE.txt`
Font Awesome Free is multi-licensed by file type:
| Component | License | Notes |
| --- | --- | --- |
| Icons (SVG/JS glyph designs) | **CC BY 4.0** (https://creativecommons.org/licenses/by/4.0/) | Attribution to Fonticons, Inc. required. Satisfied by this notice + the retained CSS header. |
| Fonts (`.woff2` webfonts) | **SIL OFL 1.1** (https://scripts.sil.org/OFL) | Distributed with the OFL text (in `LICENSE.txt`). Font files are shipped **unmodified**; the reserved font name "Font Awesome" is retained. |
| Code (CSS) | **MIT** (https://opensource.org/licenses/MIT) | Copyright header retained in `all.min.css`. |
**Compliance notes for distribution / white-labeling:**
- The vendored `.woff2` font files are byte-for-byte unmodified. Do not subset,
re-generate, or rename them — doing so triggers the SIL OFL Reserved Font Name
restriction.
- The license header comment in `all.min.css` must be preserved.
- Attribution may live in this notices file and the licenses page rather than in
the visible (white-labeled) UI; it must remain accessible to end users.
+1
View File
@@ -0,0 +1 @@
01.00.00
+42
View File
@@ -0,0 +1,42 @@
<!-- SPDX-License-Identifier: GPL-3.0-or-later -->
# MokoGIT Versioning
MokoGIT follows the **MokoOrgStandards `xx.xx.xx`** scheme: `MAJOR.MINOR.PATCH`, **two digits
per segment** (e.g. `01.00.00`, `01.04.12`). This is MokoGIT's own version line — it deliberately
**replaces the inherited upstream Gitea version** (which previously leaked through as
`1.24.0+dev-<N>-g<hash>`).
## Where the version comes from
The binary version is injected at build time into `main.Version``setting.AppVer` (drives the
footer, `/api/v1/version`, and the PWA manifest). The Makefile resolves it in this order:
| Build type | Source | Result |
| --- | --- | --- |
| **Release (git tag)** | the tag name (`$GITHUB_REF_NAME`, `v` stripped) | e.g. tag `01.00.00``01.00.00` |
| **Branch / dev** | the repo-root **`VERSION`** file (`STORED_VERSION`) | `01.00.00` |
| _(fallback only if `VERSION` is absent)_ | `git describe --tags` | would leak the Gitea base — avoided by shipping `VERSION` |
The committed `VERSION` file (currently `01.00.00`) is what strips the Gitea base on ordinary
branch builds. **Do not delete it.**
## Release tagging — MUST use `xx.xx.xx`
Release/tag automation MUST create tags in the `xx.xx.xx` form (e.g. `01.00.00`). Because the
deploy workflows derive the container image tag from `git describe --tags`, tagging with
`01.00.00` makes the deployed image report MokoGIT's version rather than Gitea's. Tiers append a
suffix: dev → `-dev`, rc → `-rc`.
## Update checker
The upstream update-checker defaults to **disabled** (`modules/setting/setting.go`) — a fork must
not compare its `AppVer` against Gitea's release feed. Re-enable only against a MokoGIT endpoint.
## Scope of a bump
- **PATCH** — fixes, no behavior change.
- **MINOR** — backward-compatible features (default bump for feature/dev → stable releases).
- **MAJOR** — breaking changes.
Per-stack and per-tier `VERSION` files also live under `deploy/git/` for the `.vault` git stack.
+3 -3
View File
@@ -86,9 +86,9 @@ type AppVersion struct {
func NewMainApp(appVer AppVersion) *cli.Command {
app := &cli.Command{}
app.Name = "gitea" // must be lower-cased because it appears in the "USAGE" section like "gitea doctor [command [command options]]"
app.Name = "mokogit" // must be lower-cased because it appears in the "USAGE" section like "mokogit doctor [command [command options]]" and in `mokogit --version`
app.Usage = "A painless self-hosted Git service"
app.Description = `Gitea program contains "web" and other subcommands. If no subcommand is given, it starts the web server by default. Use "web" subcommand for more web server arguments, use other subcommands for other purposes.`
app.Description = `MokoGIT program contains "web" and other subcommands. If no subcommand is given, it starts the web server by default. Use "web" subcommand for more web server arguments, use other subcommands for other purposes.`
app.Version = appVer.Version + appVer.Extra
app.EnableShellCompletion = true
app.Flags = []cli.Flag{
@@ -96,7 +96,7 @@ func NewMainApp(appVer AppVersion) *cli.Command {
Name: "work-path",
Aliases: []string{"w"},
TakesFile: true,
Usage: "Set Gitea's working path (defaults to the Gitea's binary directory)",
Usage: "Set MokoGIT's working path (defaults to the MokoGIT's binary directory)",
},
&cli.StringFlag{
Name: "config",
+2 -2
View File
@@ -1346,8 +1346,8 @@ LEVEL = Info
;; Whether the email of the user should be shown in the Explore Users page
;SHOW_USER_EMAIL = true
;;
;; Set the default theme for the Gitea install
;DEFAULT_THEME = gitea-auto
;; Set the default theme for the MokoGIT install
;DEFAULT_THEME = mokogit-auto
;;
;; All available themes. Allow users select personalized themes regardless of the value of `DEFAULT_THEME`.
;; Leave it empty to allow users to select any theme from "{CustomPath}/public/assets/css/theme-*.css"
+49
View File
@@ -0,0 +1,49 @@
<!-- SPDX-License-Identifier: GPL-3.0-or-later -->
# MokoGIT git stack (staged for `.vault`)
This directory is the **staged server compose** for the MokoGIT git service, laid out to match the
target `.vault/stacks/git/{prod,rc,dev}/` structure so it can be dropped into `.vault` when the
restructure resumes.
## Ownership split
- **This repo** (the Gitea fork) builds + pushes the **image** (`mokoconsulting/mokogit`, tiers
`mokogit` / `-rc` / `-dev`) via its own CI.
- **`.vault`** (`MokoConsulting/.vault`, `/opt/.vault` on the Beelink) owns the **server compose +
config** that RUNS those containers. `.vault` is the single source of truth for the box.
## Layout
```
deploy/git/
VERSION # per-stack version (xx.xx.xx)
prod/ { docker-compose.yml, VERSION, .env.example } -> container mokogit, /opt/mokogit/prod, db mokogit
rc/ { docker-compose.yml, VERSION, .env.example } -> container mokogit-rc, /opt/mokogit/rc, db mokogit_rc
dev/ { docker-compose.yml, VERSION, .env.example } -> container mokogit-dev, /opt/mokogit/dev, db mokogit_dev
```
Versioning follows MokoOrgStandards `xx.xx.xx` (see repo-root `VERSIONING.md`). Each tier and the
stack carry a `VERSION` file.
## ⚠️ Not yet live — reconcile first
These composes are **modeled on the documented live config**, not copied from the running box, so
they are **NOT guaranteed byte-accurate**. Before dropping into `.vault`:
1. **DIFF against the live** `/opt/mokogitea/docker-compose.yml` (run with `docker compose -p
gitea-dev`). Reconcile every line tagged `RECONCILE` in the compose: exact volume/conf layout
(live app.ini is at `${GIT_DATA_DIR}/conf/app.ini`), host ports, the DB host reachability (DB is
**host MySQL**; app DB user is `mokogitea`), the docker network name, and any `act_runner` /
`depends_on` wiring (note: `depends_on` uses the prod service key — mismatches caused a past
outage).
2. **Secrets stay external** — every host/secret value is `${VAR}`; real values go in a **gitignored
`.env`** or Gitea Actions secrets, never committed. `.env.example` files carry only placeholders.
## Do NOT relocate yet
The `.vault` restructure is **PAUSED** on server drift (partly from the mokogitea→mokogit rebrand
leaving the box tree dirty) and an **open critical secrets-in-git issue** in `.vault` history. Moving
the git compose onto the box now risks breaking the `--ff-only` pull. Relocation happens when the
restructure resumes — coordinate first. Until then git keeps running from `/opt/mokogitea` +
`/opt/gitea-dev` (the old locations).
+1
View File
@@ -0,0 +1 @@
01.00.00
+27
View File
@@ -0,0 +1,27 @@
# MokoGIT git stack -- DEV tier env (EXAMPLE -- copy to .env and fill; .env is gitignored)
# SPDX-License-Identifier: GPL-3.0-or-later
# No real secrets here. RECONCILE all values against the live box before use.
GIT_REGISTRY=git.mokoconsulting.tech
GIT_IMAGE=mokoconsulting/mokogit
MOKOGIT_DEV_TAG=01.00.00-dev # dev image tag. Deploy CI pins this per build.
GIT_UID=1000
GIT_GID=1000
GIT_DOMAIN=dev.git.mokoconsulting.tech
GIT_ROOT_URL=https://dev.git.mokoconsulting.tech/
GIT_SSH_DOMAIN=dev.git.mokoconsulting.tech
GIT_SSH_PUBLIC_PORT=2918
GIT_HTTP_PORT=3002
GIT_SSH_PORT=2224
GIT_DATA_DIR=/opt/mokogit/dev
GIT_DB_HOST=host.docker.internal:3306
GIT_DB_NAME=mokogit_dev
GIT_DB_USER=mokogitea
GIT_DB_PASSWD=__SET_IN_REAL_ENV__
GIT_NETWORK=gitea-dev_default
+1
View File
@@ -0,0 +1 @@
01.00.00
+47
View File
@@ -0,0 +1,47 @@
# MokoGIT git stack -- DEV tier
# SPDX-License-Identifier: GPL-3.0-or-later
#
# STAGED for .vault/stacks/git/dev/ -- see ../README.md. Modeled on the documented live config;
# DIFF against the live /opt/mokogitea/docker-compose.yml before dropping into .vault.
# Every host/secret value is ${VAR} (define real values in a gitignored .env; see .env.example).
services:
git:
image: "${GIT_REGISTRY}/${GIT_IMAGE}:${MOKOGIT_DEV_TAG}"
container_name: mokogit-dev
restart: unless-stopped
environment:
- USER_UID=${GIT_UID}
- USER_GID=${GIT_GID}
- GITEA__server__DOMAIN=${GIT_DOMAIN}
- GITEA__server__ROOT_URL=${GIT_ROOT_URL}
- GITEA__server__SSH_DOMAIN=${GIT_SSH_DOMAIN}
- GITEA__server__SSH_PORT=${GIT_SSH_PUBLIC_PORT}
- GITEA__database__DB_TYPE=mysql
- GITEA__database__HOST=${GIT_DB_HOST}
- GITEA__database__NAME=${GIT_DB_NAME}
- GITEA__database__USER=${GIT_DB_USER}
- GITEA__database__PASSWD=${GIT_DB_PASSWD}
volumes:
# RECONCILE: live app.ini at ${GIT_DATA_DIR}/conf/app.ini (/opt/mokogit/dev/conf).
- "${GIT_DATA_DIR}:/data"
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
ports:
- "${GIT_HTTP_PORT}:3000"
- "${GIT_SSH_PORT}:22"
healthcheck:
test: ["CMD", "curl", "-fsS", "http://localhost:3000/api/healthz"]
interval: 30s
timeout: 10s
retries: 5
start_period: 30s
extra_hosts:
- "host.docker.internal:host-gateway"
networks:
- git
networks:
git:
name: ${GIT_NETWORK}
external: true
+34
View File
@@ -0,0 +1,34 @@
# MokoGIT git stack -- PROD tier env (EXAMPLE -- copy to .env and fill real values; .env is gitignored)
# SPDX-License-Identifier: GPL-3.0-or-later
# No real secrets in this file. RECONCILE all values against the live box before use.
# --- image ---
GIT_REGISTRY=git.mokoconsulting.tech
GIT_IMAGE=mokoconsulting/mokogit
MOKOGIT_TAG=01.00.00 # prod image tag (release xx.xx.xx). Deploy CI pins this per build.
# --- runtime user ---
GIT_UID=1000
GIT_GID=1000
# --- server / URLs ---
GIT_DOMAIN=git.mokoconsulting.tech
GIT_ROOT_URL=https://git.mokoconsulting.tech/
GIT_SSH_DOMAIN=git.mokoconsulting.tech
GIT_SSH_PUBLIC_PORT=2918 # public SSH port advertised to clients
# --- host ports (container 3000/22 mapped to host) ---
GIT_HTTP_PORT=3000
GIT_SSH_PORT=2222
# --- data ---
GIT_DATA_DIR=/opt/mokogit/prod
# --- database (HOST MySQL; app DB user is 'mokogitea') ---
GIT_DB_HOST=host.docker.internal:3306
GIT_DB_NAME=mokogit
GIT_DB_USER=mokogitea
GIT_DB_PASSWD=__SET_IN_REAL_ENV__ # real value ONLY in gitignored .env / Actions secret
# --- docker network (external; RECONCILE actual name) ---
GIT_NETWORK=gitea-dev_default
+1
View File
@@ -0,0 +1 @@
01.00.00
+52
View File
@@ -0,0 +1,52 @@
# MokoGIT git stack -- PROD tier
# SPDX-License-Identifier: GPL-3.0-or-later
#
# STAGED for .vault/stacks/git/prod/ -- see ../README.md. Modeled on the documented live
# config; DIFF against the live /opt/mokogitea/docker-compose.yml before dropping into .vault.
# Ownership: THIS repo builds+pushes the image; .vault runs it. Every host/secret value is ${VAR}
# (define real values in a gitignored .env; see .env.example). Do NOT commit a real .env.
#
# RECONCILE before use: exact volume/conf layout, ports, DB host reachability (host MySQL),
# network name, and any act_runner/depends_on wiring are modeled -- verify against the box.
services:
git:
image: "${GIT_REGISTRY}/${GIT_IMAGE}:${MOKOGIT_TAG}"
container_name: mokogit
restart: unless-stopped
environment:
- USER_UID=${GIT_UID}
- USER_GID=${GIT_GID}
- GITEA__server__DOMAIN=${GIT_DOMAIN}
- GITEA__server__ROOT_URL=${GIT_ROOT_URL}
- GITEA__server__SSH_DOMAIN=${GIT_SSH_DOMAIN}
- GITEA__server__SSH_PORT=${GIT_SSH_PUBLIC_PORT}
# DB is HOST MySQL (RECONCILE host reachability: host-gateway / socket / LAN IP).
- GITEA__database__DB_TYPE=mysql
- GITEA__database__HOST=${GIT_DB_HOST}
- GITEA__database__NAME=${GIT_DB_NAME}
- GITEA__database__USER=${GIT_DB_USER}
- GITEA__database__PASSWD=${GIT_DB_PASSWD}
volumes:
# RECONCILE: live app.ini is at ${GIT_DATA_DIR}/conf/app.ini (/opt/mokogit/prod/conf).
- "${GIT_DATA_DIR}:/data"
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
ports:
- "${GIT_HTTP_PORT}:3000"
- "${GIT_SSH_PORT}:22"
healthcheck:
test: ["CMD", "curl", "-fsS", "http://localhost:3000/api/healthz"]
interval: 30s
timeout: 10s
retries: 5
start_period: 30s
extra_hosts:
- "host.docker.internal:host-gateway"
networks:
- git
networks:
git:
name: ${GIT_NETWORK}
external: true
+27
View File
@@ -0,0 +1,27 @@
# MokoGIT git stack -- RC tier env (EXAMPLE -- copy to .env and fill; .env is gitignored)
# SPDX-License-Identifier: GPL-3.0-or-later
# No real secrets here. RECONCILE all values against the live box before use.
GIT_REGISTRY=git.mokoconsulting.tech
GIT_IMAGE=mokoconsulting/mokogit
MOKOGIT_RC_TAG=01.00.00-rc # rc image tag. Deploy CI pins this per build.
GIT_UID=1000
GIT_GID=1000
GIT_DOMAIN=rc.git.mokoconsulting.tech
GIT_ROOT_URL=https://rc.git.mokoconsulting.tech/
GIT_SSH_DOMAIN=rc.git.mokoconsulting.tech
GIT_SSH_PUBLIC_PORT=2918
GIT_HTTP_PORT=3001
GIT_SSH_PORT=2223
GIT_DATA_DIR=/opt/mokogit/rc
GIT_DB_HOST=host.docker.internal:3306
GIT_DB_NAME=mokogit_rc
GIT_DB_USER=mokogitea
GIT_DB_PASSWD=__SET_IN_REAL_ENV__
GIT_NETWORK=gitea-dev_default
+1
View File
@@ -0,0 +1 @@
01.00.00
+47
View File
@@ -0,0 +1,47 @@
# MokoGIT git stack -- RC tier
# SPDX-License-Identifier: GPL-3.0-or-later
#
# STAGED for .vault/stacks/git/rc/ -- see ../README.md. Modeled on the documented live config;
# DIFF against the live /opt/mokogitea/docker-compose.yml before dropping into .vault.
# Every host/secret value is ${VAR} (define real values in a gitignored .env; see .env.example).
services:
git:
image: "${GIT_REGISTRY}/${GIT_IMAGE}:${MOKOGIT_RC_TAG}"
container_name: mokogit-rc
restart: unless-stopped
environment:
- USER_UID=${GIT_UID}
- USER_GID=${GIT_GID}
- GITEA__server__DOMAIN=${GIT_DOMAIN}
- GITEA__server__ROOT_URL=${GIT_ROOT_URL}
- GITEA__server__SSH_DOMAIN=${GIT_SSH_DOMAIN}
- GITEA__server__SSH_PORT=${GIT_SSH_PUBLIC_PORT}
- GITEA__database__DB_TYPE=mysql
- GITEA__database__HOST=${GIT_DB_HOST}
- GITEA__database__NAME=${GIT_DB_NAME}
- GITEA__database__USER=${GIT_DB_USER}
- GITEA__database__PASSWD=${GIT_DB_PASSWD}
volumes:
# RECONCILE: live app.ini at ${GIT_DATA_DIR}/conf/app.ini (/opt/mokogit/rc/conf).
- "${GIT_DATA_DIR}:/data"
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
ports:
- "${GIT_HTTP_PORT}:3000"
- "${GIT_SSH_PORT}:22"
healthcheck:
test: ["CMD", "curl", "-fsS", "http://localhost:3000/api/healthz"]
interval: 30s
timeout: 10s
retries: 5
start_period: 30s
extra_hosts:
- "host.docker.internal:host-gateway"
networks:
- git
networks:
git:
name: ${GIT_NETWORK}
external: true
+1
View File
@@ -447,6 +447,7 @@ func prepareMigrationTasks() []*migration {
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),
newMigration(369, "Rename mokogitea-actions system user to mokogit-actions", v1_27.RenameActionsUserToMokoGit),
newMigration(370, "Remap saved gitea-* user themes to mokogit-*", v1_27.RemapGiteaThemesToMokogit),
}
return preparedMigrations
}
+36
View File
@@ -0,0 +1,36 @@
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later
package v1_27
import "xorm.io/xorm"
// RemapGiteaThemesToMokogit rewrites each user's saved theme from the retired
// upstream "gitea-*" theme names to their "mokogit-*" equivalents, part of the
// MokoGitea -> MokoGIT rebrand. The branding rebrand deleted theme-gitea-*.css,
// so any user still pointing at a gitea-* theme would load a now-missing
// stylesheet (a blank/unstyled page). Each user's light/dark/auto preference is
// preserved; the colorblind variants (which MokoGIT does not ship) collapse to
// the matching base scheme. Idempotent: rows already on a mokogit-* value are
// left untouched by the WHERE clause on the old name.
func RemapGiteaThemesToMokogit(x *xorm.Engine) error {
sess := x.NewSession()
defer sess.Close()
if err := sess.Begin(); err != nil {
return err
}
remap := []struct{ old, new string }{
{"gitea-auto", "mokogit-auto"},
{"gitea-auto-colorblind", "mokogit-auto"},
{"gitea-light", "mokogit-light"},
{"gitea-light-colorblind", "mokogit-light"},
{"gitea-dark", "mokogit-dark"},
{"gitea-dark-colorblind", "mokogit-dark"},
}
for _, m := range remap {
if _, err := sess.Exec("UPDATE `user` SET `theme` = ? WHERE `theme` = ?", m.new, m.old); err != nil {
return err
}
}
return sess.Commit()
}
+5 -5
View File
@@ -34,11 +34,11 @@ var (
Enabled: true,
DefaultActionsURL: defaultActionsURLGitHub,
SkipWorkflowStrings: []string{"[skip ci]", "[ci skip]", "[no ci]", "[skip actions]", "[actions skip]"},
// TODO(MokoGIT rebrand): workflow discovery is temporarily kept at
// .mokogitea/workflows so the currently-deployed (old) binary can discover
// and run the deploy workflows during the cutover. Once the MokoGIT image is
// live, switch this and the workflow directory to .mokogit/workflows.
WorkflowDirs: []string{".mokogitea/workflows", ".gitea/workflows", ".github/workflows"},
// MokoGIT: custom workflow-discovery path is .mokogit/workflows (the legacy
// .mokogitea/workflows name is fully retired — do not re-add it). Fixes #798,
// where the deployed binary scanned only .mokogitea/workflows so the org's
// .mokogit/workflows CI was invisible. Override via [actions] WORKFLOW_DIRS.
WorkflowDirs: []string{".mokogit/workflows", ".gitea/workflows", ".github/workflows"},
MaxRerunAttempts: defaultMaxRerunAttempts,
}
)
+1 -1
View File
@@ -112,7 +112,7 @@ func Test_WorkflowDirs(t *testing.T) {
{
name: "default",
iniStr: `[actions]`,
wantDirs: []string{".mokogitea/workflows", ".gitea/workflows", ".github/workflows"},
wantDirs: []string{".mokogit/workflows", ".gitea/workflows", ".github/workflows"},
},
{
name: "single dir",
+3 -1
View File
@@ -187,7 +187,9 @@ func loadCommonSettingsFrom(cfg ConfigProvider) error {
func loadUpdateCheckerFrom(cfg ConfigProvider) {
sec := cfg.Section("update_checker")
UpdateChecker.Enabled = sec.Key("ENABLED").MustBool(true)
// MokoGIT is a fork with its own xx.xx.xx version scheme (see repo VERSION / VERSIONING.md);
// default the upstream update-checker OFF so we never compare AppVer against Gitea's release feed.
UpdateChecker.Enabled = sec.Key("ENABLED").MustBool(false)
UpdateChecker.Endpoint = sec.Key("ENDPOINT").MustString(UpdateChecker.Endpoint)
UpdateChecker.Channel = sec.Key("CHANNEL").MustString(UpdateChecker.Channel)
}
+1 -1
View File
@@ -95,7 +95,7 @@ var UI = struct {
CodeCommentLines: 4,
ReactionMaxUserNum: 10,
MaxDisplayFileSize: 8388608,
DefaultTheme: `gitea-auto`,
DefaultTheme: `mokogit-auto`,
FileIconTheme: `material`,
FolderIconTheme: `basic`,
Reactions: []string{`+1`, `-1`, `laugh`, `hooray`, `confused`, `heart`, `rocket`, `eyes`},
+12
View File
@@ -3,6 +3,18 @@
"dashboard": "Dashboard",
"explore_title": "Explore",
"help": "Help",
"mokogit.theme_toggle": "Toggle light / dark theme",
"mokogit.a11y_title": "Accessibility",
"mokogit.a11y_text_size": "Text size",
"mokogit.a11y_decrease": "Decrease text size",
"mokogit.a11y_increase": "Increase text size",
"mokogit.a11y_reset": "Reset",
"mokogit.a11y_invert": "Invert colors",
"mokogit.a11y_contrast": "High contrast",
"mokogit.a11y_links": "Highlight links",
"mokogit.a11y_readable": "Readable font",
"mokogit.a11y_pause": "Pause animations",
"mokogit.a11y_reset_all": "Reset all",
"logo": "Logo",
"sign_in": "Sign In",
"sign_in_with_provider": "Sign in with %s",
+122 -6
View File
@@ -31,20 +31,136 @@ func SiteManifest(w http.ResponseWriter, req *http.Request) {
ctx := req.Context()
absoluteAssetURL := strings.TrimSuffix(httplib.MakeAbsoluteURL(ctx, setting.StaticURLPrefix), "/")
appURL := strings.TrimSuffix(httplib.GuessCurrentAppURL(ctx), "/")
// Icons resolve to admin-uploaded branding (nav-icon writes logo.png, favicon
// upload writes favicon.*) when set, otherwise to the bundled MokoGIT branded
// defaults at the same /assets/img/ paths. See routers/web/admin/branding.go.
manifest := map[string]any{
"name": setting.AppName,
"short_name": setting.AppName,
"start_url": httplib.GuessCurrentAppURL(ctx),
"name": setting.AppName,
"short_name": setting.AppName,
"description": setting.AppName + " — self-hosted Git service",
"id": appURL + "/",
"start_url": appURL + "/",
"scope": appURL + "/",
"display": "standalone",
"display_override": []string{"standalone", "minimal-ui", "browser"},
"orientation": "any",
"theme_color": "#112855", // MokoGIT signature navy
"background_color": "#0e1318",
"categories": []string{"developer", "productivity"},
"icons": []map[string]string{
{"src": absoluteAssetURL + "/assets/img/favicon.svg", "type": "image/svg+xml", "sizes": "any", "purpose": "any"},
{"src": absoluteAssetURL + "/assets/img/favicon.png", "type": "image/png", "sizes": "256x256"},
{"src": absoluteAssetURL + "/assets/img/logo-small.png", "type": "image/png", "sizes": "512x512"},
{"src": absoluteAssetURL + "/assets/img/logo.png", "type": "image/png", "sizes": "512x512"},
{"src": absoluteAssetURL + "/assets/img/favicon.png", "type": "image/png", "sizes": "256x256", "purpose": "any"},
{"src": absoluteAssetURL + "/assets/img/logo-small.png", "type": "image/png", "sizes": "512x512", "purpose": "any"},
{"src": absoluteAssetURL + "/assets/img/logo-small.png", "type": "image/png", "sizes": "512x512", "purpose": "maskable"},
{"src": absoluteAssetURL + "/assets/img/logo.png", "type": "image/png", "sizes": "512x512", "purpose": "any"},
},
"shortcuts": []map[string]any{
{"name": "Dashboard", "url": appURL + "/", "icons": []map[string]string{{"src": absoluteAssetURL + "/assets/img/logo-small.png", "sizes": "512x512"}}},
{"name": "Issues", "url": appURL + "/issues"},
{"name": "Pull Requests", "url": appURL + "/pulls"},
{"name": "Explore", "url": appURL + "/explore/repos"},
},
}
_ = json.NewEncoder(w).Encode(manifest)
}
// serviceWorkerJS is the MokoGIT PWA service worker. Tokens are replaced per
// request so the scope and offline/asset URLs honor AppSubURL. Strategy:
// - navigation requests: network-first, fall back to the cached offline page
// - same-origin static assets under the asset prefix: cache-first
// - everything else: passthrough
const serviceWorkerJS = `
const CACHE = '__CACHE__';
const OFFLINE_URL = '__OFFLINE__';
const ASSET_PREFIX = '__ASSETS__';
self.addEventListener('install', (e) => {
e.waitUntil(caches.open(CACHE).then((c) => c.add(new Request(OFFLINE_URL, {cache: 'reload'}))).then(() => self.skipWaiting()));
});
self.addEventListener('activate', (e) => {
e.waitUntil(caches.keys().then((keys) => Promise.all(keys.filter((k) => k !== CACHE).map((k) => caches.delete(k)))).then(() => self.clients.claim()));
});
self.addEventListener('fetch', (e) => {
const req = e.request;
if (req.method !== 'GET') return;
const url = new URL(req.url);
if (url.origin !== self.location.origin) return;
if (req.mode === 'navigate') {
e.respondWith(fetch(req).catch(() => caches.match(OFFLINE_URL)));
return;
}
if (ASSET_PREFIX && url.pathname.startsWith(ASSET_PREFIX)) {
e.respondWith(caches.match(req).then((cached) => cached || fetch(req).then((resp) => {
if (resp && resp.ok && resp.type === 'basic') { const clone = resp.clone(); caches.open(CACHE).then((c) => c.put(req, clone)); }
return resp;
}).catch(() => cached)));
}
});
`
// ServiceWorker serves the PWA service worker script with a root-scoped
// Service-Worker-Allowed header so it can control navigations under AppSubURL.
func ServiceWorker(w http.ResponseWriter, req *http.Request) {
scope := setting.AppSubURL + "/"
assetPrefix := strings.TrimSuffix(setting.StaticURLPrefix, "/") + "/assets/"
js := serviceWorkerJS
js = strings.ReplaceAll(js, "__CACHE__", "mokogit-pwa-"+setting.AppStartTime.Format("20060102150405"))
js = strings.ReplaceAll(js, "__OFFLINE__", setting.AppSubURL+"/-/offline")
js = strings.ReplaceAll(js, "__ASSETS__", assetPrefix)
w.Header().Set("Content-Type", "application/javascript; charset=utf-8")
w.Header().Set("Service-Worker-Allowed", scope)
if httpcache.HandleGenericETagPublicCache(req, w, "", &setting.AppStartTime) {
return
}
if req.Method == http.MethodHead {
return
}
_, _ = w.Write([]byte(js))
}
// Offline serves a self-contained, branded offline fallback page. It is
// precached by the service worker and shown when a navigation fails offline.
func Offline(w http.ResponseWriter, req *http.Request) {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
if req.Method == http.MethodHead {
return
}
page := strings.ReplaceAll(offlineHTML, "__NAME__", setting.AppName)
page = strings.ReplaceAll(page, "__ICON__", strings.TrimSuffix(setting.StaticURLPrefix, "/")+"/assets/img/logo.png")
_, _ = w.Write([]byte(page))
}
// offlineHTML is a dependency-light offline page (inline styles, MokoGIT navy).
const offlineHTML = `<!DOCTYPE html>
<html lang="en" data-theme="mokogit-auto">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Offline &middot; __NAME__</title>
<style>
:root { color-scheme: light dark; }
html,body { height:100%; margin:0; }
body { display:flex; align-items:center; justify-content:center; font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica Neue',Arial,sans-serif; background:#0e1318; color:#e6ebf1; text-align:center; padding:2rem; }
.card { max-width:28rem; }
img { width:72px; height:72px; object-fit:contain; margin-bottom:1.25rem; }
h1 { font-size:1.5rem; margin:0 0 .5rem; color:#f1f5f9; }
p { color:#c7ccd4; line-height:1.5; margin:0 0 1.5rem; }
button { background:#010156; color:#fff; border:0; border-radius:.25rem; padding:.6rem 1.25rem; font-size:1rem; cursor:pointer; }
button:hover { background:#010149; }
.bar { height:4px; background:#112855; position:fixed; top:0; left:0; right:0; }
</style>
</head>
<body>
<div class="bar"></div>
<div class="card">
<img src="__ICON__" alt="" onerror="this.style.display='none'">
<h1>You're offline</h1>
<p>__NAME__ can't be reached right now. Check your connection and try again &mdash; recently visited pages may still be available.</p>
<button onclick="location.reload()">Retry</button>
</div>
</body>
</html>`
func SSHInfo(rw http.ResponseWriter, req *http.Request) {
if !git.DefaultFeatures().SupportProcReceive {
rw.WriteHeader(http.StatusNotFound)
+60 -2
View File
@@ -14,6 +14,7 @@ import (
"code.mokoconsulting.tech/MokoConsulting/MokoGIT/models/renderhelper"
repo_model "code.mokoconsulting.tech/MokoConsulting/MokoGIT/models/repo"
"code.mokoconsulting.tech/MokoConsulting/MokoGIT/modules/git"
"code.mokoconsulting.tech/MokoConsulting/MokoGIT/modules/gitrepo"
"code.mokoconsulting.tech/MokoConsulting/MokoGIT/modules/log"
"code.mokoconsulting.tech/MokoConsulting/MokoGIT/modules/markup/markdown"
"code.mokoconsulting.tech/MokoConsulting/MokoGIT/modules/setting"
@@ -170,23 +171,37 @@ func prepareOrgProfileReadme(ctx *context.Context, prepareResult *shared_user.Pr
viewAs := ctx.FormString("view_as", util.Iif(ctx.Org.IsMember, "member", "public"))
viewAsMember := viewAs == "member"
// Prefer the repo that exists for the chosen view; the internal wiki landing
// (below) is rendered even when the legacy root README.md has been removed.
var profileRepo *repo_model.Repository
var readmeBlob *git.Blob
if viewAsMember {
if prepareResult.ProfilePrivateReadmeBlob != nil {
if prepareResult.ProfilePrivateRepo != nil {
profileRepo, readmeBlob = prepareResult.ProfilePrivateRepo, prepareResult.ProfilePrivateReadmeBlob
} else {
profileRepo, readmeBlob = prepareResult.ProfilePublicRepo, prepareResult.ProfilePublicReadmeBlob
viewAsMember = false
}
} else {
if prepareResult.ProfilePublicReadmeBlob != nil {
if prepareResult.ProfilePublicRepo != nil {
profileRepo, readmeBlob = prepareResult.ProfilePublicRepo, prepareResult.ProfilePublicReadmeBlob
} else {
profileRepo, readmeBlob = prepareResult.ProfilePrivateRepo, prepareResult.ProfilePrivateReadmeBlob
viewAsMember = true
}
}
if profileRepo == nil {
return false
}
// Merged profile+wiki: render the internal wiki's landing page as the org
// Overview (resolution: home -> index -> readme -> profile). Falls back to the
// legacy root README.md below if the wiki has no landing page or errors.
if renderOrgWikiLanding(ctx, profileRepo) {
ctx.Data["IsViewingOrgAsMember"] = viewAsMember
return true
}
if readmeBlob == nil {
return false
}
@@ -208,3 +223,46 @@ func prepareOrgProfileReadme(ctx *context.Context, prepareResult *shared_user.Pr
ctx.Data["IsViewingOrgAsMember"] = viewAsMember
return true
}
// renderOrgWikiLanding renders the profile repo's internal wiki landing page as
// the org Overview content. It looks for the first existing landing page in the
// order home -> index -> readme -> profile (.md) in the repo's attached wiki and,
// if found, renders it into ctx.Data["ProfileReadmeContent"]. Returns false (with
// no error surfaced) when the wiki or a landing page is absent, so the caller can
// safely fall back to the legacy root README.md.
func renderOrgWikiLanding(ctx *context.Context, profileRepo *repo_model.Repository) bool {
wikiGitRepo, err := gitrepo.RepositoryFromRequestContextOrOpen(ctx, profileRepo.WikiStorageRepo())
if err != nil {
return false
}
branch := profileRepo.DefaultWikiBranch
if branch == "" {
branch = setting.Repository.DefaultBranch
}
commit, err := wikiGitRepo.GetBranchCommit(branch)
if err != nil {
return false
}
// Wiki pages are stored as "<Name>.md"; try common casings for each landing name.
for _, name := range []string{"Home", "home", "Index", "index", "README", "Readme", "readme", "Profile", "profile"} {
blob, err := commit.GetBlobByPath(name + ".md")
if err != nil || blob == nil {
continue
}
content, err := blob.GetBlobContent(setting.UI.MaxDisplayFileSize)
if err != nil {
return false
}
rctx := renderhelper.NewRenderContextRepoFile(ctx, profileRepo, renderhelper.RepoFileOptions{
CurrentRefPath: path.Join("branch", util.PathEscapeSegments(branch)),
})
ctx.Data["ProfileReadmeContent"], err = markdown.RenderString(rctx, content)
if err != nil {
return false
}
return true
}
return false
}
+2
View File
@@ -276,6 +276,8 @@ func Routes() *web.Router {
routes.Head("/", misc.DummyOK) // for health check - doesn't need to be passed through gzip handler
routes.Methods("GET, HEAD", "/assets/site-manifest.json", misc.SiteManifest)
routes.Methods("GET, HEAD", "/serviceworker.js", misc.ServiceWorker) // PWA service worker (root-scoped)
routes.Methods("GET, HEAD", "/-/offline", misc.Offline) // PWA offline fallback page
routes.Methods("GET, HEAD, OPTIONS", "/assets/*", routing.MarkLogLevelTrace, public.AssetsCors(), public.FileHandlerFunc())
routes.Methods("GET, HEAD", "/avatars/*", avatarStorageHandler(setting.Avatar.Storage, "avatars", storage.Avatars))
routes.Methods("GET, HEAD", "/repo-avatars/*", avatarStorageHandler(setting.RepoAvatar.Storage, "repo-avatars", storage.RepoAvatars))
+2 -2
View File
@@ -3,8 +3,8 @@ import type {Config} from 'stylelint';
const cssVarFiles = [
fileURLToPath(new URL('web_src/css/base.css', import.meta.url)),
fileURLToPath(new URL('web_src/css/themes/theme-gitea-light.css', import.meta.url)),
fileURLToPath(new URL('web_src/css/themes/theme-gitea-dark.css', import.meta.url)),
fileURLToPath(new URL('web_src/css/themes/theme-mokogit-light.css', import.meta.url)),
fileURLToPath(new URL('web_src/css/themes/theme-mokogit-dark.css', import.meta.url)),
];
export default {
+2 -2
View File
@@ -18,8 +18,8 @@ function extractRootVars(css: string) {
}
const vars = extractRootVars([
readFileSync(new URL('web_src/css/themes/theme-gitea-light.css', import.meta.url), 'utf8'),
readFileSync(new URL('web_src/css/themes/theme-gitea-dark.css', import.meta.url), 'utf8'),
readFileSync(new URL('web_src/css/themes/theme-mokogit-light.css', import.meta.url), 'utf8'),
readFileSync(new URL('web_src/css/themes/theme-mokogit-dark.css', import.meta.url), 'utf8'),
].join('\n'));
export default {
+12
View File
@@ -5,6 +5,11 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{if .Title}}{{.Title}} - {{end}}{{.PageTitleCommon}}</title>
<link rel="manifest" href="{{AssetUrlPrefix}}/site-manifest.json">
<meta name="theme-color" content="#112855">{{/* MokoGIT navy installed PWA / mobile browser chrome */}}
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="apple-mobile-web-app-title" content="{{AppName}}">
<meta name="author" content="{{if .Repository}}{{.Owner.Name}}{{else}}{{MetaAuthor}}{{end}}">
<meta name="description" content="{{if .Repository}}{{.Repository.Name}}{{if .Repository.Description}} - {{.Repository.Description}}{{end}}{{else}}{{MetaDescription}}{{end}}">
<meta name="keywords" content="{{MetaKeywords}}">
@@ -23,6 +28,13 @@
{{template "base/head_style" .}}
{{template "base/head_script" .}}
{{template "custom/header" .}}
<script nonce="{{ctx.CspScriptNonce}}">
if ('serviceWorker' in navigator) {
window.addEventListener('load', function () {
navigator.serviceWorker.register('{{AppSubUrl}}/serviceworker.js', {scope: '{{AppSubUrl}}/'}).catch(function () {});
});
}
</script>
</head>
<body>
{{template "custom/body_outer_pre" .}}
+30
View File
@@ -40,6 +40,36 @@
<!-- the full dropdown menus -->
<div class="navbar-right">
{{/* MokoGIT: accessibility menu toggle (client-side, all users) */}}
<button type="button" class="item mokogit-a11y-toggle" id="mokogit-a11y-toggle"
aria-haspopup="dialog" aria-expanded="false" aria-controls="mokogit-a11y-panel"
data-tooltip-content="{{ctx.Locale.Tr "mokogit.a11y_title"}}"
aria-label="{{ctx.Locale.Tr "mokogit.a11y_title"}}"
data-label-title="{{ctx.Locale.Tr "mokogit.a11y_title"}}"
data-label-textsize="{{ctx.Locale.Tr "mokogit.a11y_text_size"}}"
data-label-decrease="{{ctx.Locale.Tr "mokogit.a11y_decrease"}}"
data-label-increase="{{ctx.Locale.Tr "mokogit.a11y_increase"}}"
data-label-reset="{{ctx.Locale.Tr "mokogit.a11y_reset"}}"
data-label-invert="{{ctx.Locale.Tr "mokogit.a11y_invert"}}"
data-label-contrast="{{ctx.Locale.Tr "mokogit.a11y_contrast"}}"
data-label-links="{{ctx.Locale.Tr "mokogit.a11y_links"}}"
data-label-readable="{{ctx.Locale.Tr "mokogit.a11y_readable"}}"
data-label-pause="{{ctx.Locale.Tr "mokogit.a11y_pause"}}"
data-label-resetall="{{ctx.Locale.Tr "mokogit.a11y_reset_all"}}">
<span class="flex-text-block"><i class="fa-solid fa-universal-access"></i></span>
</button>
{{/* MokoGIT: light/dark theme toggle */}}
<button type="button" class="item mokogit-theme-toggle" id="mokogit-theme-toggle"
data-tooltip-content="{{ctx.Locale.Tr "mokogit.theme_toggle"}}"
aria-label="{{ctx.Locale.Tr "mokogit.theme_toggle"}}">
<span class="flex-text-block"><i class="fa-solid fa-sun"></i><i class="fa-solid fa-moon"></i></span>
</button>
{{if .IsSigned}}
<form id="mokogit-theme-form" method="post" action="{{AppSubUrl}}/user/settings/appearance/theme" class="tw-hidden">
{{.CsrfTokenHtml}}
<input type="hidden" name="theme" value="">
</form>
{{end}}
{{if and .IsSigned .MustChangePassword}}
<div class="ui dropdown jump item" data-tooltip-content="{{ctx.Locale.Tr "user_profile_and_more"}}">
<span class="text">
+6
View File
@@ -0,0 +1,6 @@
{{/* MokoGIT brand override hook (ships by default).
Drop a stylesheet at custom/public/assets/css/mokogit-custom.css on any instance to re-skin the
MokoGIT brand tokens LIVE, with no rebuild -- Gitea serves /assets/ with custom/ taking precedence.
If no override file is present the link is a harmless 404 (no visual/functional impact).
Instance admins may still override this whole partial via custom/templates/custom/header.tmpl. */}}
<link rel="stylesheet" href="{{AppSubUrl}}/assets/css/mokogit-custom.css">
+2 -2
View File
@@ -68,11 +68,11 @@
<div class="field">
<div class="ui radio checkbox">
<input class="enable-system-radio" name="wiki_mode" type="radio" value="" data-context="#external_wiki_box" data-target="#internal_wiki_box" {{if eq .Org.WikiMode ""}}checked{{end}}>
<label>Internal wiki (uses <code>.profile</code> / <code>.profile-private</code> repo wikis)</label>
<label>Internal wiki (uses <code>.mokogit</code> / <code>.mokogit-private</code> repo wikis)</label>
</div>
</div>
<div id="internal_wiki_box" class="field tw-pl-4 {{if ne .Org.WikiMode ""}}disabled{{end}}">
<p class="help">Enable the wiki on <code>.profile</code> (public) and/or <code>.profile-private</code> (members-only) repos.</p>
<p class="help">Enable the wiki on <code>.mokogit</code> (public) and/or <code>.mokogit-private</code> (members-only) repos.</p>
</div>
<div class="field">
<div class="ui radio checkbox">
+2 -1
View File
@@ -166,7 +166,8 @@
</a>
{{end}}
{{if .Permission.CanRead ctx.Consts.RepoUnitTypeWiki}}
{{/* MokoGIT: the internal-wiki (.mokogit/.mokogit-private) surfaces as the org Overview, so hide its per-repo Wiki tab. */}}
{{if and (.Permission.CanRead ctx.Consts.RepoUnitTypeWiki) (not (or (eq .Repository.Name ".mokogit") (eq .Repository.Name ".mokogit-private")))}}
<a class="{{if .PageIsWiki}}active {{end}}item" href="{{.RepoLink}}/wiki">
{{svg "octicon-book"}} {{ctx.Locale.Tr "repo.wiki"}}
</a>
+1 -1
View File
@@ -92,7 +92,7 @@
<span class="status-button-text">{{ctx.Locale.Tr "repo.issues.reopen_issue"}}</span>
</button>
{{else}}
<select name="status_id" class="ui compact dropdown" style="min-width:140px;padding:7px 10px;border:1px solid var(--color-secondary);border-radius:4px;background:var(--color-body);">
<select name="status_id" class="issue-status-select">
<option value="">-- {{ctx.Locale.Tr "repo.issues.status"}} --</option>
{{range .IssueStatusDefs}}
{{if not .ClosesIssue}}
+140
View File
@@ -0,0 +1,140 @@
/* MokoGIT accessibility menu panel UI + the effect classes toggled on <html>.
Ported from the mokoconsulting.tech / MokoOnyx accessibility widget. */
/* ---- navbar toggle button ---- */
.mokogit-a11y-toggle,
.mokogit-theme-toggle {
background: none;
border: none;
cursor: pointer;
color: inherit;
}
/* theme toggle: show sun in dark mode (click -> go light), moon in light mode */
.mokogit-theme-toggle .fa-moon { display: inline-block; }
.mokogit-theme-toggle .fa-sun { display: none; }
.mokogit-theme-toggle.is-dark .fa-moon { display: none; }
.mokogit-theme-toggle.is-dark .fa-sun { display: inline-block; }
/* ---- panel ---- */
.a11y-panel {
position: fixed;
z-index: 1000;
min-width: 260px;
max-width: 320px;
padding: 12px;
background: var(--color-body);
color: var(--color-text);
border: 1px solid var(--color-secondary);
border-radius: var(--border-radius, 4px);
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
}
.a11y-panel-title {
font-weight: 600;
margin-bottom: 8px;
padding-bottom: 8px;
border-bottom: 1px solid var(--color-secondary);
}
.a11y-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
padding: 6px 0;
}
.a11y-row-label { font-size: 0.95em; }
.a11y-size-controls { display: flex; align-items: center; gap: 4px; }
.a11y-size-btn {
min-width: 28px;
height: 28px;
padding: 0 8px;
border: 1px solid var(--color-secondary);
border-radius: var(--border-radius, 4px);
background: var(--color-secondary-bg, var(--color-box-body));
color: inherit;
cursor: pointer;
}
.a11y-size-value { min-width: 44px; text-align: center; font-variant-numeric: tabular-nums; }
/* toggle switch */
.a11y-switch {
position: relative;
width: 40px;
height: 22px;
border-radius: 11px;
border: 1px solid var(--color-secondary);
background: var(--color-secondary);
cursor: pointer;
transition: background 0.15s ease;
flex: 0 0 auto;
}
.a11y-switch::after {
content: "";
position: absolute;
top: 2px;
left: 2px;
width: 16px;
height: 16px;
border-radius: 50%;
background: #fff;
transition: transform 0.15s ease;
}
.a11y-switch.is-on { background: var(--color-primary); border-color: var(--color-primary); }
.a11y-switch.is-on::after { transform: translateX(18px); }
.a11y-reset-all {
margin-top: 10px;
width: 100%;
padding: 6px 10px;
border: 1px solid var(--color-secondary);
border-radius: var(--border-radius, 4px);
background: var(--color-secondary-bg, var(--color-box-body));
color: inherit;
cursor: pointer;
}
/* ==== effect classes toggled on <html> ==== */
/* invert colors (keep media upright) */
html.a11y-inverted { filter: invert(1) hue-rotate(180deg); }
html.a11y-inverted img,
html.a11y-inverted video,
html.a11y-inverted picture,
html.a11y-inverted canvas,
html.a11y-inverted [style*="background-image"] { filter: invert(1) hue-rotate(180deg); }
/* high contrast */
html.a11y-high-contrast { filter: contrast(1.3); }
html.a11y-high-contrast a { text-decoration: underline; }
/* highlight links */
html.a11y-highlight-links a {
text-decoration: underline !important;
outline: 1px solid currentcolor;
outline-offset: 1px;
background: rgba(255, 235, 59, 0.25);
}
/* readable font */
html.a11y-readable-font,
html.a11y-readable-font * {
font-family: Verdana, Tahoma, "Segoe UI", Arial, sans-serif !important;
letter-spacing: 0.02em;
line-height: 1.7;
}
/* pause animations */
html.a11y-pause-animations *,
html.a11y-pause-animations *::before,
html.a11y-pause-animations *::after {
animation-duration: 0s !important;
animation-iteration-count: 1 !important;
transition-duration: 0s !important;
scroll-behavior: auto !important;
}
+9
View File
@@ -1,6 +1,10 @@
@import "./modules/normalize.css";
@import "./modules/animations.css";
/* Vendored Font Awesome 7 Free (unmodified). Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT.
License + attribution: web_src/css/vendor/fontawesome/LICENSE.txt and THIRD-PARTY-NOTICES.md. */
@import "./vendor/fontawesome/css/all.min.css";
/* fomantic replacements */
@import "./modules/button.css";
@import "./modules/container.css";
@@ -49,6 +53,7 @@
@import "./features/cropper.css";
@import "./features/console.css";
@import "./features/captcha.css";
@import "./features/a11y-menu.css";
@import "./markup/content.css";
@import "./markup/codeblock.css";
@@ -65,6 +70,7 @@
@import "./repo/issue-card.css";
@import "./repo/issue-label.css";
@import "./repo/issue-list.css";
@import "./repo/issue-status.css";
@import "./repo/list-header.css";
@import "./repo/file-view.css";
@import "./repo/wiki.css";
@@ -87,4 +93,7 @@
@import "./helpers.css";
/* MokoGIT brand reskin — last so it overrides component modules (Tailwind utilities still win). */
@import "./mokogit-brand.css";
@tailwind utilities;
+87
View File
@@ -0,0 +1,87 @@
/* MokoGIT brand reskin layer
*
* Applies the mokoconsulting.tech design language on top of Gitea's component
* modules, using theme tokens (theme-mokogit-{light,dark}.css) wherever a
* variable exists so light/dark and runtime overrides "just work".
*
* Sourced from A:\MokoOnyx (the mokoconsulting.tech template):
* - fonts: system stack + Roboto (Gitea's --fonts-proportional already matches)
* - radius: .25rem base (Gitea --border-radius is already 4px)
* - soft elevation on cards/dropdowns/modals
* - buttons: weight 400, .6rem/1rem padding
* - deep-navy nav + footer (set via --color-nav-bg in the theme files)
*
* Load order: imported last in index.css so it overrides component modules;
* Tailwind utilities still win (declared after this).
*/
:root {
/* Exact mokoconsulting.tech font stack for parity (Gitea falls back to this). */
--fonts-override: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans";
/* mokoconsulting soft-elevation shadow scale (referenced by the rules below
* and available for custom overrides). */
--mokogit-shadow-sm: 0 .125rem .25rem rgba(0, 0, 0, .075);
--mokogit-shadow: 0 .5rem 1rem rgba(0, 0, 0, .15);
--mokogit-shadow-lg: 0 1rem 3rem rgba(0, 0, 0, .175);
}
/* Heavier elevation on dark surfaces — explicit dark theme + auto-following-system. */
:root[data-theme="mokogit-dark"] {
--mokogit-shadow-sm: 0 .125rem .25rem rgba(0, 0, 0, .25);
--mokogit-shadow: 0 .5rem 1rem rgba(0, 0, 0, .5);
--mokogit-shadow-lg: 0 1rem 3rem rgba(0, 0, 0, .6);
}
@media (prefers-color-scheme: dark) {
:root[data-theme="mokogit-auto"] {
--mokogit-shadow-sm: 0 .125rem .25rem rgba(0, 0, 0, .25);
--mokogit-shadow: 0 .5rem 1rem rgba(0, 0, 0, .5);
--mokogit-shadow-lg: 0 1rem 3rem rgba(0, 0, 0, .6);
}
}
/* ---- Buttons: mokoconsulting weight + padding ---- */
.ui.button,
.ui.buttons .button {
font-weight: 400;
}
/* ---- Soft elevation on floating surfaces ---- */
.ui.card,
.ui.cards > .card,
.ui.segment {
box-shadow: var(--mokogit-shadow-sm);
}
.ui.dropdown .menu,
.ui.menu .dropdown.item .menu {
box-shadow: var(--mokogit-shadow);
}
.ui.modal {
box-shadow: var(--mokogit-shadow-lg);
}
/* ---- Navbar: brand weight for the site title ---- */
#navbar .navbar-brand,
#navbar .brand {
font-weight: 600;
}
/* ---- Footer: navy brand band (color already via --color-footer) ---- */
.page-footer,
footer.page-footer {
color: var(--color-nav-text);
}
.page-footer a {
color: var(--color-nav-text);
opacity: .85;
}
.page-footer a:hover {
opacity: 1;
}
/* ---- Links: mokoconsulting underline affordance in prose ---- */
.markup a:not(.btn):not([class^="ui "]) {
text-decoration-thickness: from-font;
}
+17
View File
@@ -0,0 +1,17 @@
/* Custom issue-status control.
Kept as a plain native <select> (NOT a fomantic ".ui dropdown"): when this
element carried the "ui compact dropdown" class, fomantic enhanced it into an
overlay menu that rendered mispositioned and behind .issue-content, so the
status options were present in the DOM but invisible. A native <select>
renders its option list via the browser, so the options always show.
Styles moved here from an inline style= attribute on the element. */
.issue-status-select {
min-width: 140px;
padding: 7px 10px;
color: var(--color-text);
border: 1px solid var(--color-secondary);
border-radius: 4px;
background: var(--color-body);
}
@@ -1,8 +0,0 @@
@import "./theme-gitea-light-protanopia-deuteranopia.css" (prefers-color-scheme: light);
@import "./theme-gitea-dark-protanopia-deuteranopia.css" (prefers-color-scheme: dark);
gitea-theme-meta-info {
--theme-display-name: "Auto";
--theme-colorblind-type: "red-green";
--theme-color-scheme: "auto";
}
@@ -1,8 +0,0 @@
@import "./theme-gitea-light-tritanopia.css" (prefers-color-scheme: light);
@import "./theme-gitea-dark-tritanopia.css" (prefers-color-scheme: dark);
gitea-theme-meta-info {
--theme-display-name: "Auto";
--theme-colorblind-type: "blue-yellow";
--theme-color-scheme: "auto";
}
-7
View File
@@ -1,7 +0,0 @@
@import "./theme-gitea-light.css" (prefers-color-scheme: light);
@import "./theme-gitea-dark.css" (prefers-color-scheme: dark);
gitea-theme-meta-info {
--theme-display-name: "Auto";
--theme-color-scheme: "auto";
}
@@ -1,19 +0,0 @@
@import "./theme-gitea-dark.css";
gitea-theme-meta-info {
--theme-display-name: "Dark";
--theme-colorblind-type: "red-green";
--theme-color-scheme: "dark";
}
/* red/green colorblind-friendly colors */
:root {
--color-diff-added-fg: #58a6ff;
--color-diff-added-linenum-bg: #243d5d;
--color-diff-added-row-bg: #132339;
--color-diff-added-word-bg: #214d87;
--color-diff-removed-fg: #f0883e;
--color-diff-removed-linenum-bg: #5b361c;
--color-diff-removed-row-bg: #3c2419;
--color-diff-removed-word-bg: #824e1f;
}
@@ -1,15 +0,0 @@
@import "./theme-gitea-dark.css";
gitea-theme-meta-info {
--theme-display-name: "Dark";
--theme-colorblind-type: "blue-yellow";
--theme-color-scheme: "dark";
}
/* blue/yellow colorblind-friendly colors */
:root {
--color-diff-added-fg: #58a6ff;
--color-diff-added-linenum-bg: #243d5d;
--color-diff-added-row-bg: #132339;
--color-diff-added-word-bg: #214d87;
}
@@ -1,19 +0,0 @@
@import "./theme-gitea-light.css";
gitea-theme-meta-info {
--theme-display-name: "Light";
--theme-colorblind-type: "red-green";
--theme-color-scheme: "light";
}
/* red/green colorblind-friendly colors */
:root {
--color-diff-added-fg: #2185d0;
--color-diff-added-linenum-bg: #b6e3ff;
--color-diff-added-row-bg: #ddf4ff;
--color-diff-added-word-bg: #b6e3ff;
--color-diff-removed-fg: #fc6500;
--color-diff-removed-linenum-bg: #ffd8b5;
--color-diff-removed-row-bg: #fff1e5;
--color-diff-removed-word-bg: #ffd8b5;
}
@@ -1,15 +0,0 @@
@import "./theme-gitea-light.css";
gitea-theme-meta-info {
--theme-display-name: "Light";
--theme-colorblind-type: "blue-yellow";
--theme-color-scheme: "light";
}
/* blue/yellow colorblind-friendly colors */
:root {
--color-diff-added-fg: #2185d0;
--color-diff-added-linenum-bg: #b6e3ff;
--color-diff-added-row-bg: #ddf4ff;
--color-diff-added-word-bg: #b6e3ff;
}
@@ -0,0 +1,7 @@
@import "./theme-mokogit-light.css" (prefers-color-scheme: light);
@import "./theme-mokogit-dark.css" (prefers-color-scheme: dark);
gitea-theme-meta-info {
--theme-display-name: "MokoGIT Auto";
--theme-color-scheme: "auto";
}
@@ -1,37 +1,38 @@
gitea-theme-meta-info {
--theme-display-name: "Dark";
--theme-display-name: "MokoGIT Dark";
--theme-color-scheme: "dark";
}
:root {
--is-dark-theme: true;
--color-primary: #4183c4;
/* MokoGIT brand primary (dark): bright sky-blue accent (mokoconsulting.tech --accent-color-primary #3f8ff0), readable on dark bg. Nav uses #112855 below. */
--color-primary: #3f8ff0;
--color-primary-contrast: #ffffff;
--color-primary-dark-1: #548fca;
--color-primary-dark-2: #679cd0;
--color-primary-dark-3: #7aa8d6;
--color-primary-dark-4: #8db5dc;
--color-primary-dark-5: #b3cde7;
--color-primary-dark-6: #d9e6f3;
--color-primary-dark-7: #f4f8fb;
--color-primary-light-1: #3876b3;
--color-primary-light-2: #31699f;
--color-primary-light-3: #2b5c8b;
--color-primary-light-4: #254f77;
--color-primary-light-5: #193450;
--color-primary-light-6: #0c1a28;
--color-primary-light-7: #04080c;
--color-primary-alpha-10: #4183c419;
--color-primary-alpha-20: #4183c433;
--color-primary-alpha-30: #4183c44b;
--color-primary-alpha-40: #4183c466;
--color-primary-alpha-50: #4183c480;
--color-primary-alpha-60: #4183c499;
--color-primary-alpha-70: #4183c4b3;
--color-primary-alpha-80: #4183c4cc;
--color-primary-alpha-90: #4183c4e1;
--color-primary-hover: var(--color-primary-light-1);
--color-primary-active: var(--color-primary-light-2);
--color-primary-dark-1: #58a0f5;
--color-primary-dark-2: #71b0f8;
--color-primary-dark-3: #8bbffa;
--color-primary-dark-4: #a5cffb;
--color-primary-dark-5: #c5e0fd;
--color-primary-dark-6: #e0eefe;
--color-primary-dark-7: #f3f8ff;
--color-primary-light-1: #3579cf;
--color-primary-light-2: #2c66b0;
--color-primary-light-3: #234f88;
--color-primary-light-4: #1a3a63;
--color-primary-light-5: #14304f;
--color-primary-light-6: #0c1c30;
--color-primary-light-7: #060e18;
--color-primary-alpha-10: #3f8ff019;
--color-primary-alpha-20: #3f8ff033;
--color-primary-alpha-30: #3f8ff04b;
--color-primary-alpha-40: #3f8ff066;
--color-primary-alpha-50: #3f8ff080;
--color-primary-alpha-60: #3f8ff099;
--color-primary-alpha-70: #3f8ff0b3;
--color-primary-alpha-80: #3f8ff0cc;
--color-primary-alpha-90: #3f8ff0e1;
--color-primary-hover: var(--color-primary-dark-1);
--color-primary-active: var(--color-primary-dark-2);
--color-secondary: #3f4248;
--color-secondary-dark-1: #46494f;
--color-secondary-dark-2: #4f5259;
@@ -72,14 +73,14 @@ gitea-theme-meta-info {
--color-console-menu-bg: #292b2e;
--color-console-menu-border: #46494f;
--color-console-link: #969aa1;
/* named colors */
--color-red: #cc4848;
--color-orange: #cc580c;
--color-yellow: #cc9903;
/* named colors - MokoGIT/mokoconsulting.tech brand palette (dark tuned) */
--color-red: #ff7a73;
--color-orange: #ff9c4d;
--color-yellow: #ffd166;
--color-olive: #91a313;
--color-green: #87ab63;
--color-teal: #00918a;
--color-blue: #3a8ac6;
--color-green: #78d694;
--color-teal: #76e3ff;
--color-blue: #91a4ff;
--color-violet: #906ae1;
--color-purple: #b259d0;
--color-pink: #d22e8b;
@@ -207,11 +208,11 @@ gitea-theme-meta-info {
--color-orange-badge-hover-bg: #f2711c4d;
--color-git: #f05133;
--color-logo: #609926;
/* target-based colors */
--color-body: #1e1f20;
--color-box-header: #1b1c1e;
--color-box-body: #161718;
--color-box-body-highlight: #202124;
/* target-based colors - MokoGIT navy-dark surfaces (mokoconsulting.tech) */
--color-body: #0e1318;
--color-box-header: #151b22;
--color-box-body: #10151b;
--color-box-body-highlight: #151b22;
--color-text-dark: #f8f8f8;
--color-text: #d2d4d8;
--color-text-light: #c0c2c7;
@@ -238,7 +239,7 @@ gitea-theme-meta-info {
--color-code-bg: #161718;
--color-shadow: #0b0b0c58;
--color-shadow-opaque: #0b0b0c;
--color-secondary-bg: #2e3033;
--color-secondary-bg: #151b22;
--color-expand-button: #333539;
--color-placeholder-text: var(--color-text-light-3);
--color-editor-line-highlight: var(--color-secondary-alpha-40);
@@ -250,9 +251,10 @@ gitea-theme-meta-info {
--color-reaction-active-bg: var(--color-primary-light-5);
--color-tooltip-text: #fafafa;
--color-tooltip-bg: #0b0b0cf0;
--color-nav-bg: #18191b;
--color-nav-hover-bg: var(--color-secondary-light-1);
--color-nav-text: var(--color-text);
/* MokoGIT brand: signature deep-navy top nav + footer (matches mokoconsulting.tech) */
--color-nav-bg: #112855;
--color-nav-hover-bg: rgba(255, 255, 255, 0.12);
--color-nav-text: #ffffff;
--color-secondary-nav-bg: #1a1b1e;
--color-label-text: var(--color-text);
--color-label-bg: #7a7f8a4b;
@@ -308,6 +310,20 @@ gitea-theme-meta-info {
--color-syntax-traceback: #ff8686;
--color-syntax-matching-bracket-bg: #00918a48;
--color-syntax-nonmatching-bracket-bg: #cc484848;
/* ===== MokoOnyx surface separation (navy-tinted elevation) =====
Establish a clear 3-step elevation so panels separate from the page:
page (darkest) -> box body -> box header/secondary (lightest). All tones
are navy-tinted (blue channel raised) to stay cohesive with the #112855
brand nav, mirroring mokoconsulting.tech's dark surface hierarchy. */
--color-body: #0b1017; /* page: deepest navy-black */
--color-box-body: #121a24; /* cards/boxes: lifted, navy-tinted */
--color-box-header: #17212e; /* box headers: lightest surface */
--color-box-body-highlight: #17212e;
--color-secondary-bg: #17212e;
--color-card: #141d28;
--color-menu: #141d28;
--color-button: #141d28;
--color-light-border: #ffffff24; /* more visible panel edges on dark */
accent-color: var(--color-accent);
color-scheme: dark;
}
@@ -1,35 +1,36 @@
gitea-theme-meta-info {
--theme-display-name: "Light";
--theme-display-name: "MokoGIT Light";
--theme-color-scheme: "light";
}
:root {
--is-dark-theme: false;
--color-primary: #4183c4;
/* MokoGIT brand primary: deep navy (mokoconsulting.tech --primary #010156). Nav uses #112855 below. */
--color-primary: #010156;
--color-primary-contrast: #ffffff;
--color-primary-dark-1: #3876b3;
--color-primary-dark-2: #31699f;
--color-primary-dark-3: #2b5c8b;
--color-primary-dark-4: #254f77;
--color-primary-dark-5: #193450;
--color-primary-dark-6: #0c1a28;
--color-primary-dark-7: #04080c;
--color-primary-light-1: #548fca;
--color-primary-light-2: #679cd0;
--color-primary-light-3: #7aa8d6;
--color-primary-light-4: #8db5dc;
--color-primary-light-5: #b3cde7;
--color-primary-light-6: #d9e6f3;
--color-primary-light-7: #f4f8fb;
--color-primary-alpha-10: #4183c419;
--color-primary-alpha-20: #4183c433;
--color-primary-alpha-30: #4183c44b;
--color-primary-alpha-40: #4183c466;
--color-primary-alpha-50: #4183c480;
--color-primary-alpha-60: #4183c499;
--color-primary-alpha-70: #4183c4b3;
--color-primary-alpha-80: #4183c4cc;
--color-primary-alpha-90: #4183c4e1;
--color-primary-dark-1: #010149;
--color-primary-dark-2: #010141;
--color-primary-dark-3: #010139;
--color-primary-dark-4: #010130;
--color-primary-dark-5: #010122;
--color-primary-dark-6: #010114;
--color-primary-dark-7: #01010a;
--color-primary-light-1: #12137a;
--color-primary-light-2: #2a2c99;
--color-primary-light-3: #4548b8;
--color-primary-light-4: #6b6ecf;
--color-primary-light-5: #b3b4de;
--color-primary-light-6: #d9daf0;
--color-primary-light-7: #f3f3fb;
--color-primary-alpha-10: #01015619;
--color-primary-alpha-20: #01015633;
--color-primary-alpha-30: #0101564b;
--color-primary-alpha-40: #01015666;
--color-primary-alpha-50: #01015680;
--color-primary-alpha-60: #01015699;
--color-primary-alpha-70: #010156b3;
--color-primary-alpha-80: #010156cc;
--color-primary-alpha-90: #010156e1;
--color-primary-hover: var(--color-primary-dark-1);
--color-primary-active: var(--color-primary-dark-2);
--color-secondary: #d0d7de;
@@ -72,14 +73,14 @@ gitea-theme-meta-info {
--color-console-menu-bg: #f8f9fb;
--color-console-menu-border: #d0d7de;
--color-console-link: #5c656d;
/* named colors */
--color-red: #db2828;
--color-orange: #f2711c;
--color-yellow: #fbbd08;
/* named colors - MokoGIT/mokoconsulting.tech brand palette */
--color-red: #a51f18;
--color-orange: #fd7e17;
--color-yellow: #ad6200;
--color-olive: #b5cc18;
--color-green: #21ba45;
--color-teal: #00b5ad;
--color-blue: #2185d0;
--color-green: #448344;
--color-teal: #5abfdd;
--color-blue: #010156;
--color-violet: #6435c9;
--color-purple: #a333c8;
--color-pink: #e03997;
@@ -238,7 +239,7 @@ gitea-theme-meta-info {
--color-code-bg: #fafdff;
--color-shadow: #00001726;
--color-shadow-opaque: #c7ced5;
--color-secondary-bg: #f2f5f8;
--color-secondary-bg: #eaedf0;
--color-expand-button: #cfe8fa;
--color-placeholder-text: var(--color-text-light-3);
--color-editor-line-highlight: var(--color-secondary-alpha-30);
@@ -250,9 +251,10 @@ gitea-theme-meta-info {
--color-reaction-active-bg: var(--color-primary-light-6);
--color-tooltip-text: #fbfdff;
--color-tooltip-bg: #000017f0;
--color-nav-bg: #f6f7fa;
--color-nav-hover-bg: var(--color-secondary-light-1);
--color-nav-text: var(--color-text);
/* MokoGIT brand: signature deep-navy top nav + footer (matches mokoconsulting.tech) */
--color-nav-bg: #112855;
--color-nav-hover-bg: rgba(255, 255, 255, 0.12);
--color-nav-text: #ffffff;
--color-secondary-nav-bg: #f9fafb;
--color-label-text: var(--color-text);
--color-label-bg: #949da64b;
@@ -308,6 +310,18 @@ gitea-theme-meta-info {
--color-syntax-traceback: #c00000;
--color-syntax-matching-bracket-bg: #00b5ad38;
--color-syntax-nonmatching-bracket-bg: #db282838;
/* ===== MokoOnyx surface separation (mokoconsulting.tech gray scale) =====
Give the page a soft gray base so white cards/boxes visibly lift off it,
with a defined gray-300 border tone for clear panel edges. Mirrors the
surface hierarchy of mokoconsulting.tech (MokoOnyx --gray-100/200/300). */
--color-body: #f0f3f6; /* page: soft gray (was #fff, blended into cards) */
--color-box-body: #ffffff; /* cards/boxes: white, now lift off the gray page */
--color-box-header: #e9edf1; /* box headers: gray-200 tone */
--color-secondary-bg: #e7ebef; /* MokoOnyx gray-200 */
--color-card: #ffffff;
--color-menu: #ffffff;
--color-button: #ffffff;
--color-light-border: #0000172e; /* stronger gray-300 border definition */
accent-color: var(--color-accent);
color-scheme: light;
}
+165
View File
@@ -0,0 +1,165 @@
Fonticons, Inc. (https://fontawesome.com)
--------------------------------------------------------------------------------
Font Awesome Free License
Font Awesome Free is free, open source, and GPL friendly. You can use it for
commercial projects, open source projects, or really almost whatever you want.
Full Font Awesome Free license: https://fontawesome.com/license/free.
--------------------------------------------------------------------------------
# Icons: CC BY 4.0 License (https://creativecommons.org/licenses/by/4.0/)
The Font Awesome Free download is licensed under a Creative Commons
Attribution 4.0 International License and applies to all icons packaged
as SVG and JS file types.
--------------------------------------------------------------------------------
# Fonts: SIL OFL 1.1 License
In the Font Awesome Free download, the SIL OFL license applies to all icons
packaged as web and desktop font files.
Copyright (c) 2025 Fonticons, Inc. (https://fontawesome.com)
with Reserved Font Name: "Font Awesome".
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
SIL OPEN FONT LICENSE
Version 1.1 - 26 February 2007
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting — in part or in whole — any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
--------------------------------------------------------------------------------
# Code: MIT License (https://opensource.org/licenses/MIT)
In the Font Awesome Free download, the MIT license applies to all non-font and
non-icon files.
Copyright 2025 Fonticons, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in the
Software without restriction, including without limitation the rights to use, copy,
modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so, subject to the
following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
# Attribution
Attribution is required by MIT, SIL OFL, and CC BY licenses. Downloaded Font
Awesome Free files already contain embedded comments with sufficient
attribution, so you shouldn't need to do anything additional when using these
files normally.
We've kept attribution comments terse, so we ask that you do not actively work
to remove them from files, especially code. They're a great way for folks to
learn about Font Awesome.
--------------------------------------------------------------------------------
# Brand Icons
All brand icons are trademarks of their respective owners. The use of these
trademarks does not indicate endorsement of the trademark holder by Font
Awesome, nor vice versa. **Please do not use brand logos for any purpose except
to represent the company, product, or service to which they refer.**
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+183
View File
@@ -0,0 +1,183 @@
// MokoGIT accessibility menu (ported from MokoOnyx / mokoconsulting.tech).
// Client-side, persisted in localStorage["moko-a11y"]. Each option toggles a
// class on <html> (except text size, which sets root font-size). Labels are read
// from data-* attributes on the toggle button so they stay localized.
type A11yState = {
fontStep: number; // index into FONT_STEPS
invert: boolean;
contrast: boolean;
links: boolean;
readable: boolean;
pauseAnim: boolean;
};
const LS_KEY = 'moko-a11y';
const FONT_STEPS = [85, 90, 100, 110, 120, 130];
const DEFAULT_STEP = 2; // 100%
const DEFAULT_STATE: A11yState = {
fontStep: DEFAULT_STEP, invert: false, contrast: false, links: false, readable: false, pauseAnim: false,
};
function load(): A11yState {
try {
return {...DEFAULT_STATE, ...JSON.parse(localStorage.getItem(LS_KEY) || '{}')};
} catch {
return {...DEFAULT_STATE};
}
}
function save(s: A11yState): void {
try {
localStorage.setItem(LS_KEY, JSON.stringify(s));
} catch {}
}
function apply(s: A11yState): void {
const el = document.documentElement;
el.style.fontSize = `${FONT_STEPS[s.fontStep] ?? 100}%`;
el.classList.toggle('a11y-inverted', s.invert);
el.classList.toggle('a11y-high-contrast', s.contrast);
el.classList.toggle('a11y-highlight-links', s.links);
el.classList.toggle('a11y-readable-font', s.readable);
el.classList.toggle('a11y-pause-animations', s.pauseAnim);
}
// Apply persisted prefs as early as this module runs, to minimize flash.
apply(load());
function makeSwitch(labelText: string, checked: boolean, onChange: (v: boolean) => void): HTMLElement {
const row = document.createElement('div');
row.className = 'a11y-row';
const label = document.createElement('span');
label.className = 'a11y-row-label';
label.textContent = labelText;
const btn = document.createElement('button');
btn.type = 'button';
btn.className = 'a11y-switch';
btn.setAttribute('role', 'switch');
btn.setAttribute('aria-checked', String(checked));
btn.setAttribute('aria-label', labelText);
const setVisual = (v: boolean) => {
btn.setAttribute('aria-checked', String(v));
btn.classList.toggle('is-on', v);
};
setVisual(checked);
btn.addEventListener('click', () => {
const v = btn.getAttribute('aria-checked') !== 'true';
setVisual(v);
onChange(v);
});
row.append(label, btn);
return row;
}
function buildPanel(btn: HTMLElement, state: A11yState): HTMLElement {
const d = btn.dataset;
const panel = document.createElement('div');
panel.className = 'a11y-panel';
panel.id = 'mokogit-a11y-panel';
panel.setAttribute('role', 'dialog');
panel.setAttribute('aria-label', d.labelTitle || 'Accessibility');
panel.hidden = true;
const title = document.createElement('div');
title.className = 'a11y-panel-title';
title.textContent = d.labelTitle || 'Accessibility';
panel.append(title);
// Text size group: - [nnn%] + reset
const sizeRow = document.createElement('div');
sizeRow.className = 'a11y-row a11y-size-row';
const sizeLabel = document.createElement('span');
sizeLabel.className = 'a11y-row-label';
sizeLabel.textContent = d.labelTextsize || 'Text size';
const controls = document.createElement('div');
controls.className = 'a11y-size-controls';
const dec = document.createElement('button');
dec.type = 'button'; dec.className = 'a11y-size-btn'; dec.textContent = '';
dec.setAttribute('aria-label', d.labelDecrease || 'Decrease text size');
const pct = document.createElement('span');
pct.className = 'a11y-size-value';
const inc = document.createElement('button');
inc.type = 'button'; inc.className = 'a11y-size-btn'; inc.textContent = '+';
inc.setAttribute('aria-label', d.labelIncrease || 'Increase text size');
const rst = document.createElement('button');
rst.type = 'button'; rst.className = 'a11y-size-btn a11y-size-reset';
rst.textContent = d.labelReset || 'Reset';
const renderPct = () => { pct.textContent = `${FONT_STEPS[state.fontStep]}%`; };
renderPct();
dec.addEventListener('click', () => { state.fontStep = Math.max(0, state.fontStep - 1); renderPct(); apply(state); save(state); });
inc.addEventListener('click', () => { state.fontStep = Math.min(FONT_STEPS.length - 1, state.fontStep + 1); renderPct(); apply(state); save(state); });
rst.addEventListener('click', () => { state.fontStep = DEFAULT_STEP; renderPct(); apply(state); save(state); });
controls.append(dec, pct, inc, rst);
sizeRow.append(sizeLabel, controls);
panel.append(sizeRow);
const commit = (k: 'invert' | 'contrast' | 'links' | 'readable' | 'pauseAnim', v: boolean) => { state[k] = v; apply(state); save(state); };
panel.append(makeSwitch(d.labelInvert || 'Invert colors', state.invert, (v) => commit('invert', v)));
panel.append(makeSwitch(d.labelContrast || 'High contrast', state.contrast, (v) => commit('contrast', v)));
panel.append(makeSwitch(d.labelLinks || 'Highlight links', state.links, (v) => commit('links', v)));
panel.append(makeSwitch(d.labelReadable || 'Readable font', state.readable, (v) => commit('readable', v)));
panel.append(makeSwitch(d.labelPause || 'Pause animations', state.pauseAnim, (v) => commit('pauseAnim', v)));
const resetAll = document.createElement('button');
resetAll.type = 'button';
resetAll.className = 'a11y-reset-all';
resetAll.textContent = d.labelResetall || 'Reset all';
resetAll.addEventListener('click', () => {
Object.assign(state, DEFAULT_STATE);
apply(state); save(state);
// rebuild to reflect reset state
panel.replaceWith(buildPanel(btn, state));
(document.querySelector('#mokogit-a11y-panel') as HTMLElement).hidden = false;
});
panel.append(resetAll);
return panel;
}
export function initA11yMenu(): void {
const btn = document.querySelector<HTMLButtonElement>('#mokogit-a11y-toggle');
if (!btn) return;
const state = load();
apply(state);
let panel = buildPanel(btn, state);
document.body.append(panel);
const positionPanel = () => {
const r = btn.getBoundingClientRect();
panel.style.top = `${Math.round(r.bottom + 6)}px`;
panel.style.right = `${Math.round(window.innerWidth - r.right)}px`;
};
const open = () => {
panel = document.querySelector('#mokogit-a11y-panel') as HTMLElement;
positionPanel();
panel.hidden = false;
btn.setAttribute('aria-expanded', 'true');
document.addEventListener('click', onDocClick, true);
document.addEventListener('keydown', onKey, true);
};
const close = () => {
(document.querySelector('#mokogit-a11y-panel') as HTMLElement).hidden = true;
btn.setAttribute('aria-expanded', 'false');
document.removeEventListener('click', onDocClick, true);
document.removeEventListener('keydown', onKey, true);
};
const onDocClick = (e: MouseEvent) => {
const p = document.querySelector('#mokogit-a11y-panel') as HTMLElement;
if (p.contains(e.target as Node) || btn.contains(e.target as Node)) return;
close();
};
const onKey = (e: KeyboardEvent) => {
if (e.key === 'Escape') { close(); btn.focus(); }
};
btn.addEventListener('click', (e) => {
e.preventDefault();
const p = document.querySelector('#mokogit-a11y-panel') as HTMLElement;
p.hidden ? open() : close();
});
}
+43
View File
@@ -0,0 +1,43 @@
// MokoGIT navbar light/dark toggle.
// Signed-in users: persists to the account via the existing
// /user/settings/appearance/theme endpoint (a hidden CSRF-protected form is
// rendered in the navbar), so the choice syncs across devices; the page reloads
// with the new theme. Anonymous users: best-effort data-theme flip + localStorage
// (only the server-served theme CSS is loaded for anon, so effect is limited).
const LIGHT = 'mokogit-light';
const DARK = 'mokogit-dark';
const LS_KEY = 'mokogit-theme';
function currentIsDark(): boolean {
const t = document.documentElement.getAttribute('data-theme') || '';
if (t.includes('dark')) return true;
if (t.includes('light')) return false;
// auto / unknown -> follow the OS preference
return window.matchMedia?.('(prefers-color-scheme: dark)').matches ?? false;
}
export function initThemeToggle(): void {
const btn = document.querySelector<HTMLButtonElement>('#mokogit-theme-toggle');
if (!btn) return;
const sync = () => btn.classList.toggle('is-dark', currentIsDark());
sync();
btn.addEventListener('click', () => {
const target = currentIsDark() ? LIGHT : DARK;
const form = document.querySelector<HTMLFormElement>('#mokogit-theme-form');
if (form) {
const input = form.querySelector<HTMLInputElement>('input[name="theme"]');
if (input) input.value = target;
form.submit(); // server persists + redirects back, re-rendering with the new theme
return;
}
// anonymous best-effort
document.documentElement.setAttribute('data-theme', target);
try {
localStorage.setItem(LS_KEY, target);
} catch {}
sync();
});
}
+5
View File
@@ -39,6 +39,8 @@ import {initInstall} from './features/install.ts';
import {initCompWebHookEditor} from './features/comp/WebHookEditor.ts';
import {initRepoBranchButton} from './features/repo-branch.ts';
import {initCommonOrganization} from './features/common-organization.ts';
import {initThemeToggle} from './features/theme-toggle.ts';
import {initA11yMenu} from './features/a11y-menu.ts';
import {initRepoWikiForm} from './features/repo-wiki.ts';
import {initRepository, initBranchSelectorTabs} from './features/repo-legacy.ts';
import {initCopyContent} from './features/copycontent.ts';
@@ -163,6 +165,9 @@ const initPerformanceTracer = callInitFunctions([
initActionsPermissionsForm,
initDevtest,
initThemeToggle,
initA11yMenu,
]);
// it must be the last one, then the "querySelectorAll" only needs to be executed once for global init functions.
+30
View File
@@ -0,0 +1,30 @@
# Third-Party & Vendored Assets
MokoGIT bundles a small number of third-party assets. Each is redistributed under
its own license, with attribution retained here, in the repository
[`THIRD-PARTY-NOTICES.md`](https://git.mokoconsulting.tech/MokoConsulting/MokoGIT/src/branch/main/THIRD-PARTY-NOTICES.md),
and alongside the vendored files.
> **White-labeling note:** these attribution obligations survive rebranding. When
> producing a white-labeled build, keep this page (or the `THIRD-PARTY-NOTICES.md`
> file) accessible to end users. You may keep attribution out of the visible UI, but
> it must remain reachable in the product's documentation or licenses listing.
## Font Awesome Free 7.1.0
- **Author / Copyright:** (c) 2025 Fonticons, Inc. -- <https://fontawesome.com>
- **Upstream license:** <https://fontawesome.com/license/free>
- **Vendored at:** `web_src/css/vendor/fontawesome/` (CSS + `.woff2` webfonts, unmodified)
- **License text shipped at:** `web_src/css/vendor/fontawesome/LICENSE.txt`
Font Awesome Free is multi-licensed by file type:
| Component | License |
| --- | --- |
| Icons (glyph designs) | CC BY 4.0 -- attribution to Fonticons, Inc. required |
| Fonts (`.woff2`) | SIL OFL 1.1 -- shipped with the OFL text; font files unmodified; reserved font name "Font Awesome" retained |
| Code (CSS) | MIT -- copyright header retained in `all.min.css` |
**Do not** subset, regenerate, or rename the vendored font files -- modifying them
triggers the SIL OFL Reserved Font Name restriction. Keep the license header comment
in `all.min.css` intact.