- NormalizeSystemRepoName: bare reserved names (mokogit, mokogit-private)
are auto-normalized to their dotted form (.mokogit, .mokogit-private),
so the org profile/wiki repos are always created as private system repos.
Applied on repo create and rename.
- ChangeRepositoryName now refuses to rename any dot-prefixed system repo to
a name without a leading dot (which would strip its system-repo/private
status).
Claude-Session: https://claude.ai/code/session_01Bqe7fAuHQeiLueYfeHFrHw
Re-apply the two org-wiki fixes onto the rebrand branch so the MokoGIT
cutover deploy does not regress them:
- drop the duplicate org/menu (org/header already renders it)
- rewrite intra-wiki links to /{owner}/-/wiki/ (relative + absolute,
nested pages), hiding the wiki repo name; reword empty-state.
Claude-Session: https://claude.ai/code/session_01Bqe7fAuHQeiLueYfeHFrHw
The currently-deployed (old) binary only discovers .mokogitea/workflows, so
the rebrand's deploy workflows must live there to run during the cutover.
Moved .mokogit/workflows -> .mokogitea/workflows and reverted the WorkflowDirs
default + path refs. Everything else stays .mokogit. TODO: switch back to
.mokogit/workflows once the MokoGIT image is live.
Claude-Session: https://claude.ai/code/session_01Bqe7fAuHQeiLueYfeHFrHw
The Organization value is derived from the org profile (repo owner) and
was shown as a read-only field; it added no value, so remove it entirely
from the settings page. The DerivedOrgName model method stays (API still
derives org on read). See #771.
Claude-Session: https://claude.ai/code/session_01Bqe7fAuHQeiLueYfeHFrHw
- Branding: default app name MokoGitea -> MokoGIT; all standalone
'MokoGitea' brand strings, comments, and copyright headers -> MokoGIT
- Special repo/config names: .mokogitea/.mokogitea-private -> .mokogit/
.mokogit-private (workflow discovery, issue/PR templates, profile+wiki
repo names in header.go, config dir renamed)
- Lowercase: mokogitea -> mokogit (docker image refs, ntfy topic, mail
tags, wiki docs, Joomla element/targetplatform, MCP package docs)
- Actions system user mokogitea-actions -> mokogit-actions + DB
migration #369 to rename the existing id=-2 user in place
- Icon: bundle Moko favicon.svg as public/assets/img/{favicon,logo}.svg;
wire PWA manifest (SiteManifest) + nav logo to the SVG
- CHANGELOG entry documenting the rebrand
Shared MOKOGITEA_* CI/compose env + org-secret names are intentionally
left for the coordinated server cutover to avoid breaking cross-repo CI.
Claude-Session: https://claude.ai/code/session_01Bqe7fAuHQeiLueYfeHFrHw
Sweep all .go imports + go.mod, plus module-path refs in .golangci.yml,
Dockerfile, swagger templates, locale example, and repo/wiki URLs
(MokoGitea-Fork -> MokoGIT). Part of the MokoGIT rebrand.
Claude-Session: https://claude.ai/code/session_01Bqe7fAuHQeiLueYfeHFrHw
The repo metadata 'platform' field was a hardcoded enum; changing the
taxonomy meant a code change + redeploy. Make it admin-configurable:
- modules/setting/metadata.go: new [metadata] PLATFORM_OPTIONS setting
(default joomla,dolibarr,go,npm,generic), loaded in loadCommonSettingsFrom
- routers/web/admin/metadata.go + templates/admin/metadata.tmpl: new
Admin -> Metadata page to edit the list (persisted to app.ini), with a
nav entry and route
- routers/web/repo/setting/metadata.go + template: platform dropdown now
reads from the setting; a repo's current value stays selectable even if
removed from the list (no silent drop)
Closes#777
Deploy workflows in go/npm repos live in the workflows root, not custom/
(mirrors npm repos' npm-publish.yml in root). The sync's curated set does
not include deploy-*, so root placement is not clobbered. Moved: deploy-dev.yml, deploy-mokogitea.yml, deploy-rc.yml
These are repo-specific (not platform-standard) and belong in custom/ so
the workflow sync never distributes/clobbers them. They still run here
(Gitea Actions executes custom/). Moved: deploy-rc.yml
sync-on-merge.yml and workflow-sync-trigger.yml are template-only (gated
if: Template-*), dead no-ops in this child repo, inherited at scaffold time.
sync-on-merge.yml is also broken. The real sync lives in the templates'
workflows/custom/.
Rewind the uploaded stream between targets and write/close each file in
turn instead of a shared io.MultiWriter, so a failure writing a later
target (e.g. logo.png) can never leave an earlier one (logo-small.png)
truncated. Addresses review feedback on #774.
The app icon (logo.png) is the PWA/web-manifest icon and the navbar
fallback, but had no Branding control, so it stayed the shipped default
even on a fully branded instance. Uploading the Nav Icon now also writes
logo.png (single io.MultiWriter pass), and resetting the Nav Icon reverts
both to the built-in default.
Closes#773
The repo metadata org field was a stored, editable value that snapshotted
the owner name and could drift when an organization was renamed. It is now
derived from the org profile (repository owner) on read and is read-only.
- models/repo/repo.go: add Repository.DerivedOrgName (owner FullName, falling
back to the handle, then the denormalized OwnerName)
- models/repo/repo_manifest.go: drop the Org struct field
- models/migrations/v1_27/v369.go + migrations.go: migration #368 drops the
repo_manifest.org column
- routers/api/v1/repo/manifest.go: derive org in the response, ignore it on write
- routers/web/repo/setting/metadata.go + templates: show org read-only (derived)
- services/updateserver/joomla.go: use DerivedOrgName for the feed maintainer
Closes#771
Land deploy-mokogitea.yml robustness fix on main. [skip ci] — workflow-file-only change; no rebuild/redeploy needed (prod already live on stable-289 from #733).
The prod deploy (deploy-mokogitea.yml, on push to main) failed the #733 release and
silently corrupted the shared compose. Three fixes:
- Removed the 'Verify dev environment is healthy' gate: it curled git.dev and exit 1
on failure, so a dev blip false-negatives the PROD deploy (wrong-tier gate; the
dev->rc->main pipeline + RC env is the real gate).
- Replaced 'sed s|mokogitea:...|:$TAG|' (matched ALL mokogitea service lines and
clobbered the dev+rc ${MOKOGITEA_*_TAG} env-vars with the prod tag) with the
env-var pattern: env $TAG_ENV=$TAG docker compose up -- drives only the target
service, no sed.
- Added 'docker rm -f $CONTAINER' + '-p gitea-dev' + '--force-recreate' before the
compose up, fixing the 'Container name /mokogitea already in use' conflict.
Aligns deploy-mokogitea.yml with the fixed deploy-dev/deploy-rc pattern. Host compose
was separately restored to env-var form (the bad run had clobbered dev+rc tags).
Long-term: cut over Template-Go#5 deploy-prod.yml. Refs #733, #752.
Claude-Session: https://claude.ai/code/session_01Wsno14cxE49MstXFs9G5KT
The Gitea Actions scheduler on the current build does not offer the dependent
2nd job of a needs-chain to runners: the Tests job (needs: lint) reaches
'waiting' but is never claimed even with idle, label-matching runners, then is
reaped by ABANDONED_JOB_TIMEOUT. Verified via dispatch on idle runners (run
34933: lint claimed+success, Tests waiting/runner_id 0, never scheduled).
Drop 'needs: lint' + the always() gate; guard template repos directly so Tests
runs as an independent, schedulable job.
Claude-Session: https://claude.ai/code/session_01Wsno14cxE49MstXFs9G5KT
Two compile errors blocked the whole tests/integration package (prereq for the
layered test pipeline, Template-Go#4, and the clean go-compile owed by #733):
- api_packages_composer_test.go: composer.PackageMetadataResponse{} (value) used
where *PackageMetadataResponse (pointer) is required -> take the address.
- oauth_avatar_test.go: 10 stale upstream 'code.gitea.io/gitea/' imports (never
updated after the fork module rename) -> 'code.mokoconsulting.tech/MokoConsulting/MokoGitea/'.
go vet ./tests/integration/... now exits 0 (compiles clean).
Claude-Session: https://claude.ai/code/session_01Wsno14cxE49MstXFs9G5KT
The /manifest GET+PUT routes were backward-compat aliases for the canonical
/metadata routes. Completing the manifest->metadata migration (#752): drop the
alias so /metadata is the single path. Handlers (GetRepoMetadata/UpdateRepoMetadata)
are unchanged. The MCP already calls /metadata (mcp-mokogitea-api v1.4.2,
gitea_metadata_get/update); the stale gitea_manifest_* tools only exist in older
deployed MCP builds and drop out on redeploy.
Refs #752
Claude-Session: https://claude.ai/code/session_01Wsno14cxE49MstXFs9G5KT
The .mokogitea/manifest.xml file is deprecated - the first-class RepoMetadata DB
fields (set via the /manifest API) are the authoritative repository metadata (see
issue #752). Remove the legacy XML auto-sync path rather than teaching it the
renamed mokocli root element:
- delete services/repository/manifest_sync.go (manifestXML parser)
- drop the SyncMetadataFromCommit call on default-branch push (push.go)
- rebrand remaining mokoplatform->mokocli refs (repo_manifest.go, v347.go comment,
locale manifest_desc)
The RepoMetadata model, /manifest API, settings UI, and update-feed generation
(first-class fields) are unchanged. gofmt-normalized repo_manifest.go struct tags.
Refs #752
Claude-Session: https://claude.ai/code/session_01Wsno14cxE49MstXFs9G5KT
Lets deploy-rc be run on demand (from a ref carrying current source) to verify
the RC pipeline end-to-end without a full rc promotion + pre-release.
Claude-Session: https://claude.ai/code/session_01Wsno14cxE49MstXFs9G5KT
Shared DEPLOY_HOST/PORT/USER assumed all tiers live on one host. Scope every
deploy variable to the rc tier (mirrors the org's DEV_SSH_* convention) so a repo
inheriting this template can host rc/dev/prod on separate machines independently.
Claude-Session: https://claude.ai/code/session_01Wsno14cxE49MstXFs9G5KT
The registry token is the org-wide MOKOGITEA_TOKEN secret (already configured,
used by deploy-dev/deploy-mokogitea), not a new DEPLOY_REGISTRY_TOKEN. Reuse the
existing secret names so no new secret values are needed; only the non-sensitive
tier variables get set per-repo.
Claude-Session: https://claude.ai/code/session_01Wsno14cxE49MstXFs9G5KT
- Moved from custom/ to the root .mokogitea/workflows/ (synced/template-owned area).
- All deployment config now comes from repo Actions variables (DEPLOY_HOST/PORT/USER,
DEPLOY_REGISTRY/IMAGE, RC_CONTAINER/COMPOSE_PROJECT/COMPOSE_DIR/SOURCE_DIR/TAG_ENV/
HEALTH_URL) and secrets (DEPLOY_SSH_KEY, DEPLOY_REGISTRY_TOKEN) — nothing hardcoded,
so it works across Go server repos. Clone URL derived from github.server_url/repository.
- To become Template-Go-owned (Template-Go#3).
Claude-Session: https://claude.ai/code/session_01Wsno14cxE49MstXFs9G5KT
Adds a custom deploy workflow that builds and deploys the release candidate
to rc.git.mokoconsulting.tech (mokogitea-rc container, port 3110, DB
mokogitea_rc) on push to the rc branch — the branch promote-rc creates when
a PR to main is opened. Mirrors the fixed deploy-dev.yml: env-var image tag
(MOKOGITEA_RC_TAG) driving the shared compose file (no sed), rm -f before
force-recreate, quoted-heredoc remote expansion, health check + external
verify. Gives a real release-candidate environment to validate before prod.
Intended to move to Template-Go as the canonical synced source once validated
(kept in custom/ for now so it isn't overwritten by workflow sync).
Claude-Session: https://claude.ai/code/session_01Wsno14cxE49MstXFs9G5KT
Extend org-level branch protection to support per-user allowlists (resolved
from username OR email) and an "allow Actions bot" toggle, alongside the
existing team allowlists, for all five categories (push, merge, force-push,
delete, approvals).
- models/git/org_protected_branch.go: add WhitelistUserIDs, MergeWhitelistUserIDs,
ForcePushAllowlistUserIDs, DeleteAllowlistUserIDs, ApprovalsWhitelistUserIDs
([]int64) plus WhitelistActionsUser, MergeWhitelistActionsUser,
ForcePushAllowlistActionsUser, DeleteAllowlistActionsUser (bool); copy all 9
into ProtectedBranch in ToProtectedBranch().
- models/migrations/v1_27/v368.go: migration 367 adds the 9 columns.
- modules/structs/org_branch.go: add *Usernames []string and *ActionsUser bool
to Create/Edit options and the response, matching repo-level json names.
- routers/api/v1/org/branch_protection.go: resolveUserIDs (username then email,
dedupe, 422 on unknown); wire into Create + Edit and toAPIOrgBranchProtection.
- models/git/protected_branch_merge.go: add mergeAllowFlag and merge the four
Actions-user flags most-restrictively (org can now express them); deploy-key
flags stay repo-only pass-through.
- models/git/protected_branch_merge_test.go: mergeAllowFlag truth table, user-ID
intersection, deploy-key pass-through.
- regenerate swagger v1 + openapi3 specs.
Claude-Session: https://claude.ai/code/session_01Wsno14cxE49MstXFs9G5KT
The branch-protection delete feature (#696/#728) added a second
"repo.settings.event_delete" entry ("Branch Deletion") to locale_en-US.json,
reusing the existing webhook-event key (value "Delete"). The old JSON decoder
silently kept the last value; Go 1.26's jsonv2 decoder rejects duplicate
object keys, so InitLocales fails ("duplicate object member name
repo.settings.event_delete") and the server crash-loops at startup. Like the
code-scanner regexp panic, this only surfaces on a fresh boot, which is why it
shipped unnoticed.
Give the branch-protection section header its own key
"repo.settings.protect_branch_deletion" and point protected_branch.tmpl at it,
so the webhook "Delete" label and the branch-protection "Branch Deletion"
header both render correctly and the JSON has no duplicate. Verified: no
duplicate keys remain in any options/locale/*.json.
Claude-Session: https://claude.ai/code/session_01Wsno14cxE49MstXFs9G5KT
The "deserialize-yaml-py" rule in services/security/code_scanner.go used a
negative lookahead `(?!\s*#)` in regexp.MustCompile. Go's regexp engine is
RE2, which has no lookahead/lookbehind, so MustCompile panics during the
package init() — crash-looping the entire server at startup. `go build` and
`go vet` do not execute init(), and CI never boots the binary, so this
shipped to main via #552 undetected; the running instances survived only
because they predate that image.
Replace the pattern with an RE2-safe equivalent `(?i)yaml\.load\s*\(`, which
matches the rule's stated intent (flag yaml.load() without SafeLoader,
CWE-502). Add a regression test that forces the package init and asserts
every DefaultCodeRules pattern compiled, so a future RE2-incompatible
pattern fails in CI here instead of on a live deploy.
Claude-Session: https://claude.ai/code/session_01Wsno14cxE49MstXFs9G5KT
Code-review findings on the org-governance release:
- Fail closed on org-rule lookup error: getFirstMatchProtectedBranchRule
swallowed FindOrgBranchRuleForBranch errors (returned nil,nil), silently
dropping the org floor and falling back to the repo rule on a transient DB
error. Propagate the error so the org rule stays enforced.
- Stop the org rule locking out deploy-key and Actions-bot pushes:
OrgProtectedBranch is team-only, so mergeMostRestrictive was ANDing the
repo's WhitelistDeployKeys / WhitelistActionsUser (and the force-push,
delete and merge counterparts) against the org side's always-false zero
value, blocking every deploy-key and Actions push in any org with a
matching branch rule. Carry those org-unmanaged fields through from the
repo rule unchanged.
- Org push-policy max-file-size now inspects only the pushed delta
(diff-tree + cat-file --batch-check) instead of the full tip tree via
ls-tree, so a pre-existing oversized file can no longer permanently block
unrelated pushes. New branches (no base commit) still scan the full tree.
Dev deploy targeting:
- deploy-dev.yml drove the dev container image via `sed` on the SHARED
compose file, but the pattern matched the *prod* service line
(container_name: mokogitea) — leaving the dev service pinned to a stale
image (so every "green" deploy recreated old code) while corrupting the
prod image pin. Drive the dev service image from ${MOKOGITEA_DEV_TAG}
instead; the env-var only affects the dev service.
Claude-Session: https://claude.ai/code/session_01Wsno14cxE49MstXFs9G5KT
After the tag fix (#737) the dev deploy builds and pushes the image
fine but fails at `docker compose up -d` with:
Conflict. The container name "/mokogitea-dev" is already in use
The dev service uses a fixed container_name, and the symlinked
/opt/gitea-dev path makes compose's derived project name unstable, so
an existing container is not recognized as the project's and `up`
tries to create rather than recreate. Remove any lingering
fixed-name container first, pin the compose project name, and force a
fresh recreate so migrations run against the new image.
Claude-Session: https://claude.ai/code/session_01Wsno14cxE49MstXFs9G5KT
Annotate the four previously undocumented org-governance API handlers
(tag_protection, push_policy, repo_defaults, email_domain) with
swagger:operation blocks, and register the swagger:response models the
branch_protection operations already referenced. Register the org
option DTOs in the parameterBodies hack so their definitions are
emitted.
Also fix pre-existing spec-generation blockers surfaced once the spec
became regenerable: a stray comment glued to the repoUpdateManifest
swagger block (broke YAML parsing), missing owner/repo path params on
the manifest operations, a Manifest response registration, and missing
definitions for EditAccessTokenOption, the IssueBulk* options, and the
Issue{Priority,Status,Type}Def types. Regenerated v1_json.tmpl and
v1_openapi3_json.tmpl; spec now validates cleanly against Swagger 2.0.
Claude-Session: https://claude.ai/code/session_01Wsno14cxE49MstXFs9G5KT
Add an Org Governance entry to the README key-features list (org-wide
branch/tag protection, push policy, repo defaults, email-domain
allowlist) and record the recent build/CI fixes (#734, #735, #736,
#737) under CHANGELOG [Unreleased].
Claude-Session: https://claude.ai/code/session_01Wsno14cxE49MstXFs9G5KT
The dev deploy step used an unquoted SSH heredoc and referenced
runner-side values as \$TAG / \$REGISTRY_TOKEN, deferring their
expansion to the remote shell where those names are unset. The
Docker build tag collapsed to "mokogitea:" and every dev deploy
failed with `invalid tag ... invalid reference format` before any
migration or server boot could run.
Inject TAG and REGISTRY_TOKEN as an env prefix on the ssh command
(`TAG='...' REGISTRY_TOKEN='...' bash -s`) and switch to a quoted
heredoc so every $var expands in exactly one place: the remote host.
Also fixes HEALTH_FMT (was defined on the runner but referenced
remotely) and adds an explicit empty-TAG guard so a future
regression fails loudly instead of building an untagged image.
Claude-Session: https://claude.ai/code/session_01Wsno14cxE49MstXFs9G5KT
api_license_keys_test.go used the outdated NewRequestWithBody signature
(passing []byte where io.Reader is now required) — wrapped the string bodies in
strings.NewReader. Note: tests/integration remains broadly pre-existing-broken
across multiple other fork-added files (api_packages_composer type mismatch,
etc.); those are a separate dedicated cleanup, not part of #727.
Claude-Session: https://claude.ai/code/session_01Wsno14cxE49MstXFs9G5KT
`go vet ./...` (finally runnable with a local Go toolchain) surfaced three
pre-existing failures that prevented the whole test tree from compiling — which
is very likely why the "Project CI / Tests" job never went green. None relate to
#727; all pre-existing on main.
- modules/util/util_test.go: CryptoRandomInt/String/Bytes now return (value,
error); the tests used single-value assignment. Updated to capture + assert
the error (and dropped a now-redundant `var err error`).
- tests/integration/auth_oauth2_test.go: `newFakeOIDCServer` was declared twice
with different signatures (redeclaration = build failure). Renamed the
config-struct variant to `newFakeOIDCServerWithConfig` and updated its caller;
the (sub, oid) variant keeps the original name for its caller.
- routers/web/repo/issue_comment.go: removed a redundant `&& statusIDStr != ""`
duplicate condition (vet: redundant and).
Verified: `go vet ./modules/util` clean; full `go vet ./...` re-run.
Claude-Session: https://claude.ai/code/session_01Wsno14cxE49MstXFs9G5KT
Two pre-existing issues surfaced when the org-governance series was compiled
locally with a real Go toolchain (go1.26.3) for the first time:
- routers/api/v1/api.go:519 called organization.HasOrgOrUserVisible, which no
longer exists — it was renamed to IsOwnerVisibleToDoer (models/organization/
org.go:548, identical signature). This one missed call site meant the whole
routers/api/v1 package (and therefore the server binary) failed `go build`.
With the rename, `go build ./...` is clean.
- gofmt: api.go (a mis-indented commented-out /projects route block) and
release.go (import sort: repo before updateserver) were gofmt-dirty. Fixed
with gofmt -w on the two files this change already touches.
Not part of #727, but blocks building/releasing the fork; found while validating
the dev -> main promotion (#733).
Claude-Session: https://claude.ai/code/session_01Wsno14cxE49MstXFs9G5KT
package-lock.json (13.9k lines of generated npm lockfile) was swept into the
org push-policy commit (3aac1b456c, #730) by a `git add -A` during a gofmt-fix
restack. It is not tracked on main and is not part of the org-governance work.
Removing it so the dev -> main promotion (#733) doesn't introduce it.
Claude-Session: https://claude.ai/code/session_01Wsno14cxE49MstXFs9G5KT
Restricts which email domains an organization's members may have. When a policy
is configured, a user can only be added to the org (via any team) if their
primary email matches one of the allowed domain globs.
Enforced at the single membership choke point services/org.AddTeamMember, which
every add path (API, web, group-sync) funnels through — so one check covers them
all. On violation it returns a typed ErrEmailDomainNotAllowed; the API team-add
handler maps it to 422.
- models/git/org_email_domain.go: OrgEmailDomainPolicy model + EmailAllowed
(domain glob match) + OrgEmailDomainAllowed + typed error + CRUD. Migration 366.
- API: GET/PATCH/DELETE /orgs/{org}/email_domain_policy.
- Enforcement in services/org/team.go; 422 mapping in routers/api/v1/org/team.go.
An empty policy imposes no restriction. This is the one bounded piece of the
"access/security" tier; org 2FA-required and IP allowlists were deliberately NOT
built here — they are cross-cutting enforcement (auth gating / request
middleware) that needs a compiler + tests, not a blind stacked PR.
Stacked on #731/#730/#729/#728 for migration ordering (this = 366). Swagger
omitted.
Note: no Go toolchain available locally, so not compiled/gofmt'd/tested here.
Hand-verified: gofmt (tabs, no blank-in-block), imports (git_model added to the
api team handler, gci order), typed-error detection, migration contiguous (366).
Claude-Session: https://claude.ai/code/session_01Wsno14cxE49MstXFs9G5KT
Adds a single per-org repository-defaults config, applied to a repo when it is
created in or transferred into the org via a notifier (services/org):
- ForcePrivate — force new/transferred repos private (Repository.IsPrivate).
- PR defaults (when ApplyPRDefaults) — allowed merge styles, default merge
style, and auto-delete-branch-after-merge, written to the repo's pull-requests
unit config via repo_service.UpdateRepositoryUnits.
Best-effort: the notifier logs and swallows errors, so a defaults bug can never
break repository creation or transfer.
- models/git/org_repo_defaults.go: OrgRepoDefaults model + CRUD + migration 365.
- API: GET/PATCH/DELETE /orgs/{org}/repo_defaults.
- services/org/notifier.go: CreateRepository/TransferRepository -> apply defaults;
registered from routers/init.go (org_service.Init()).
Stacked on #730/#729/#728 for migration ordering (this = 365). Swagger omitted.
Note: no Go toolchain available locally, so not compiled/gofmt'd/tested here.
Hand-verified: gofmt (tabs, no blank-in-block, struct/DTO alignment), imports
used, no Init() collision in services/org, migration contiguous (365), notifier
signatures match the Notifier interface.
Claude-Session: https://claude.ai/code/session_01Wsno14cxE49MstXFs9G5KT
Adds a single per-org push policy that cascades to every repo of the org and is
enforced in the pre-receive hook:
- Branch/tag name conventions (glob) — a pushed ref name must match. Fail-closed.
- Mandatory secret-scanning block-on-push — org can force secret blocking that a
repo cannot disable (overrides the per-repo scanner config in the orchestrator).
- Max pushed-file size — rejects a tip tree containing a blob over the limit.
- Blocked file-path patterns — rejects pushes changing matching paths (reuses
pull_service.CheckFileProtection).
The two content checks (blocked paths, max size) FAIL OPEN on any error so a
policy/parsing bug can never wedge all pushes; naming is fail-closed.
- models/git/org_push_policy.go: OrgPushPolicy model + CRUD + matchers +
GetOrgPushPolicyForRepo. Migration 364.
- API: GET/PATCH/DELETE /orgs/{org}/push_policy (routers/api/v1/org/push_policy.go,
DTOs in modules/structs/org_push_policy.go, wired in api.go).
- Enforcement: routers/private/hook_pre_receive.go (branch: naming + blocked paths
+ max size; tag: naming) and services/security/orchestrator.go (secret mandate).
Deferred: a repo-facing read-only view of the org push policy (it is an org-wide
config, not per-repo overlay rules; readable via the API for now).
Stacked on #729/#728 for migration ordering (this = 364). Swagger annotations
omitted (can't regenerate without the toolchain).
Note: no Go toolchain available locally, so not compiled/gofmt'd/tested here.
Hand-verified: gofmt (tabs, no blank-in-block), escape sequences in the ls-tree
parser, imports used, migration contiguous (364), fail-open on content checks.
Claude-Session: https://claude.ai/code/session_01Wsno14cxE49MstXFs9G5KT
Adds org-level tag protection as a parallel to org-level branch protection.
An org tag rule is {NamePattern, AllowlistTeamIDs}; it cascades to every repo
in the org and layers on top of the repo's own protected tags — a tag is
controllable (push/delete) only if allowed at BOTH levels (fail-closed).
- models/git/org_protected_tag.go: OrgProtectedTag model + CRUD +
ToProtectedTag() (reuses the ProtectedTag matcher/allowlist logic) +
IsUserAllowedToControlTagInRepo() which ANDs the repo decision with the org
decision. Migration 363.
- API: /orgs/{org}/tag_protections CRUD (routers/api/v1/org/tag_protection.go,
DTOs in modules/structs/org_tag.go, wired in api.go).
- Enforcement: the git push/delete hook (hook_pre_receive.go) and the two
release paths (release.go create/delete) now call the layered check, so no
per-site tag logic changes beyond swapping the helper.
- View: the repo Tag settings page lists inherited org tag rules read-only.
Stacked on #728 (branch-protection PR) for migration ordering — merge #728
first. Swagger annotations omitted (can't regenerate the swagger JSON without
the toolchain); routes still register.
Note: no Go toolchain available locally, so not compiled/gofmt'd/tested here.
Hand-verified: gofmt (tabs, no blank-in-block, struct alignment), template
nesting balances, all .Rule fields exist on OrgProtectedTag, all locale keys
defined, JSON valid, migration contiguous (363).
Claude-Session: https://claude.ai/code/session_01Wsno14cxE49MstXFs9G5KT
Two related additions:
1. Branch deletion as an org-level ability. OrgProtectedBranch gained
CanDelete / EnableDeleteAllowlist / DeleteAllowlistTeamIDs (migration 362),
ToProtectedBranch maps them, and the API (create/edit/response DTOs +
handlers) exposes enable_delete / enable_delete_allowlist /
delete_allowlist_teams. The layering merge already combined delete fields, so
org delete-protection now enforces once ToProtectedBranch populates them.
2. The repo Branch Protection view now renders each inherited org rule as an
expandable detail (direct push, force-push, branch deletion, merge, required
approvals, status checks, protected files) with team names resolved, instead
of three headline badges. Still read-only.
Note: no Go toolchain available locally, so not compiled/gofmt'd/tested here.
Verified by hand: struct-field gofmt alignment, template block nesting balances,
every .Rule field exists on OrgProtectedBranch, and all locale keys referenced
in the template are defined.
Claude-Session: https://claude.ai/code/session_01Wsno14cxE49MstXFs9G5KT
The org "floor" is enforced implicitly at the choke point, so a repo admin
couldn't see which org-level rules apply to their repo. Surface them in the
repo's Branch Protection settings page (read-only), the way GitHub shows
organization rulesets in a repository.
- ProtectedBranchRules handler: when the owner is an org, load
FindOrgProtectedBranchRules and expose them as OrgProtectedBranches.
- branches.tmpl: new read-only "Organization Branch Protection" section listing
each org rule with an "Organization" badge, a lock/read-only marker, and
compact indicators (required approvals, signed commits, status checks). No
edit/delete controls — these are managed at the org level.
- en-US locale strings.
Note: no Go toolchain available locally, so not compiled/gofmt'd/tested here.
Claude-Session: https://claude.ai/code/session_01Wsno14cxE49MstXFs9G5KT
Org-level branch protection was already consulted at the single enforcement
choke point `GetFirstMatchProtectedBranchRule`, but only as a FALLBACK: if any
repo-level rule matched the branch, the org rule was ignored entirely. That let
a repo define a looser rule for a pattern and effectively opt out of the org's
protection.
Make the choke point LAYER the two rules instead: when both an org rule and a
repo rule match a branch, return their most-restrictive (fail-closed)
combination, so the org rule is a mandatory floor a repo can only tighten.
- models/git/protected_branch_merge.go: mergeMostRestrictive + helpers. Allow
flags AND'd; gate/require/block flags OR'd; RequiredApprovals max'd; required
sets (status contexts, protected files) unioned; allow sets (whitelists,
unprotected files) intersected. A disabled allowlist means "everyone", so it
only constrains when enabled.
- models/git/protected_branch_list.go: GetFirstMatchProtectedBranchRule now
fetches both the repo rule and the org rule and merges when both match;
returns whichever exists when only one matches. Org lookup factored into
getFirstMatchOrgProtectedBranchRule.
Supersedes the materialization approach previously proposed for this issue —
the org fallback already existed, so only this one function needed to change.
Fail-closed by design: any merge edge errs toward MORE protection (over-restrict)
rather than less, so it cannot open a hole.
Note: no Go toolchain available locally, so not compiled/gofmt'd/tested here —
relying on CI to validate build, formatting, and tests.
Claude-Session: https://claude.ai/code/session_01Wsno14cxE49MstXFs9G5KT
echo "::error::Manifest contains legacy raw/branch update server URL on MokoGitea. Use the Gitea Pages URL instead (e.g. /{REPO}/updates.xml not /{REPO}/raw/branch/main/updates.xml)"
echo "::error::Manifest contains legacy raw/branch update server URL on MokoGIT. Use the MokoGIT Pages URL instead (e.g. /{REPO}/updates.xml not /{REPO}/raw/branch/main/updates.xml)"
echo "$RAW_URLS"
exit 1
fi
@@ -496,9 +493,6 @@ jobs:
name:Build RC Package
runs-on:ubuntu-latest
needs:[branch-policy, validate]
# Run only when both gates succeeded; always() forces evaluation so a skipped
# validate (e.g. template repos) skips this job cleanly instead of hanging.
- **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
- Metadata platform options are now **admin-configurable** instead of hardcoded: a new **Admin → Metadata** page edits the allowed `platform` values (persisted to `app.ini``[metadata] PLATFORM_OPTIONS`, default `joomla,dolibarr,go,npm,generic`), and the repo Settings → Metadata platform dropdown reads from it. Changing the taxonomy no longer needs a code change/redeploy. A repo's existing platform value stays selectable even if later removed from the list (#777)
- Org branch protection: repositories now show the inherited organization rules read-only in their Branch Protection settings, with an expandable detail (direct push, force-push, branch deletion, merge restrictions, required approvals, status checks, protected files, and whitelisted teams) — like GitHub surfaces org rulesets in a repo (#727)
- Org branch protection: org-level rules can now also protect against branch deletion (`enable_delete` + delete allowlist teams), mirroring the per-repo delete allowlist (#727)
- Org-level tag protection: protect tag patterns org-wide (e.g. `v*`) with a team allowlist, layered on top of each repo's own protected tags — a tag is controllable only if allowed at both levels (fail-closed). API at `/orgs/{org}/tag_protections`; enforced at the git push/delete hook and the release create/delete paths; shown read-only in the repo Tag settings (#727)
- Org-level push policy: one policy per org, enforced in the pre-receive hook across all its repositories — branch/tag name conventions (glob), a mandatory secret-scanning block-on-push that repos cannot disable, a max pushed-file size, and blocked file-path patterns. API at `/orgs/{org}/push_policy`. Naming is fail-closed; the content checks (blocked paths, max size) fail open on error so a policy bug can never block every push (#727)
- Org-level repository defaults: an org can force new/transferred repositories private and set default pull-request settings (allowed merge styles, default merge style, auto-delete branch after merge), applied via a notifier when a repo is created in or transferred into the org (best-effort — never blocks repo creation). API at `/orgs/{org}/repo_defaults` (#727)
- Org-level email domain policy: restrict which email domains an organization's members may have — a user can only be added to the org (via any team) if their primary email matches one of the allowed domain globs. Enforced at the single membership-add choke point (`AddTeamMember`); API at `/orgs/{org}/email_domain_policy` (#727)
- Code security scanner: pattern-based detection of SQL injection, XSS, command injection, path traversal, insecure deserialization, hardcoded credentials, and weak cryptography across Go/PHP/Python/JS/TS (#552)
- Cascade merge: auto-create PRs to downstream branches after merge with configurable rules per repo (#460)
- Issue status presets: 4 built-in templates (default, software-development, support-tickets, bug-tracking) with API + web UI (#507)
- Cross-org status migration: copy status definitions from one org to another via API (#507)
- Auto-create default teams on org creation: Developers (write), Reviewers (read), CI/CD (actions+packages) (#513)
- Repo metadata `org` is now **derived from the org profile** (the repository owner) instead of being stored/editable, so it can never drift when an organization is renamed. The `org` column is dropped from `repo_manifest` (migration #368) and the field is derived on read via `Repository.DerivedOrgName` (owner display name, falling back to the handle) across the metadata API, the Settings → Metadata page (now read-only), and the Joomla update-server feed. The API `PUT` and MCP `metadata_update` now ignore `org` (read-only, like the already-derived `display_name`) (#771)
- Admin Branding: uploading a custom **Nav Icon** now also sets the **app icon** (`logo.png`, the PWA / web-manifest icon and navbar fallback), so a branded instance shows its own installable app icon instead of the shipped default; resetting the Nav Icon reverts both (#773)
- Fork server binary now compiles: `routers/api/v1/api.go` called `organization.HasOrgOrUserVisible`, which had been renamed to `IsOwnerVisibleToDoer`; the one missed call site broke `go build` of the entire `routers/api/v1` package (CI's Lint & Validate does not run a full build, so it went unnoticed) (#735)
- Dev deploy workflow: the build/deploy step referenced runner-side values as `\$TAG` / `\$REGISTRY_TOKEN` inside an unquoted SSH heredoc, deferring expansion to the remote shell where those names are unset — the Docker tag collapsed to an empty `mokogit:` and every dev deploy failed with `invalid reference format`. Runner values are now injected via an ssh env-prefix and the heredoc is quoted so each `$var` expands in exactly one place (#737)
- Repaired unit-test compile and `go vet` failures: `CryptoRandomInt/String/Bytes` now return two values (updated `modules/util/util_test.go`), removed a redundant `&&` condition in `issue_comment.go`, and cleaned up isolated integration-test compile errors (#736)
- Removed a stray `package-lock.json` (13.9k lines) that a `git add -A` had accidentally swept into the org-push-policy branch (#734)
- Org-level branch protection now **layers** with per-repo rules instead of being ignored whenever a repo rule exists. When both an org rule and a repo rule match a branch, the effective rule is the most-restrictive (fail-closed) combination — the org rule is a mandatory floor a repo cannot weaken: allow flags AND'd, gate/require/block flags OR'd, required approvals max'd, status checks and protected-file patterns unioned, whitelists intersected. Previously a repo rule shadowed the org rule entirely at the enforcement choke point (`GetFirstMatchProtectedBranchRule`), letting a repo opt out of org protection (#727)
- Org Teams page: list now renders — the handler wrote `ctx.Data["OrgListTeams"]` but the template reads `.Teams`, so the page showed header/nav but no teams (#720)
- Issue type: now editable after creation for users with issue write permission — the sidebar gated editing on a `FieldEditFlags` data key that was never populated (always read-only); now uses `HasIssuesOrPullsWritePermission` like the priority field (#721)
- Admin config form: radio inputs (e.g. instance landing page Mode) no longer throw "Unsupported config form value mapping", which had aborted all JS init on the admin settings page
- PR check branch policy: allow `fix/*` → `main` and `patch/*` → `main` to match documented policy (was rejecting fix/patch PRs to main)
- PR check platform detection: guard for missing `.mokogitea/manifest.xml` so the Validate PR job no longer aborts under `set -e` (manifest replaced by metadata API)
- Remove dangling `mcp-mokogitea-api` submodule gitlink (no `.gitmodules` entry) that broke `submodule update --init` at checkout, failing all PR build/release jobs; ignore the local clone path
- PR check platform detection: guard for missing `.mokogit/manifest.xml` so the Validate PR job no longer aborts under `set -e` (manifest replaced by metadata API)
- Remove dangling `mcp-mokogit-api` submodule gitlink (no `.gitmodules` entry) that broke `submodule update --init` at checkout, failing all PR build/release jobs; ignore the local clone path
- PR RC Release workflow: no-op cleanly when `updates.xml` is absent (generic repos) instead of aborting the "Determine RC version" step under `set -e`
- PR check: platform detection now queries metadata API instead of removed manifest.xml
- [Repo Wiki](https://git.mokoconsulting.tech/MokoConsulting/MokoGIT/wiki/) -- feature docs, API reference, operations
## Contributing
See the [org wiki](https://git.mokoconsulting.tech/MokoConsulting/.mokogitea/wiki/) for development guidelines, coding standards, and contribution instructions.
See the [org wiki](https://git.mokoconsulting.tech/MokoConsulting/.mokogit/wiki/) for development guidelines, coding standards, and contribution instructions.
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.