9.5 KiB
Decap CMS: Gitea settings
Status: applied and verified for internal use on 2026-09-04. The repository
targets the permanent cms staging branch, the CMS application is deployed
from main, and sloanops.admin is the approved internal editor. Client or
public access remains out of scope.
The repository implementation targets these fixed public values:
| Setting | Value |
|---|---|
| Gitea repository | SloanOps/3031023-xyz |
| Decap write branch | cms |
| Gitea base URL | https://git.sloanops.com |
| Gitea API root | https://git.sloanops.com/api/v1 |
| Stable CMS origin | https://cms-3031023-xyz-www.ksloan90.workers.dev |
| Production admin origin | https://3031023.xyz |
| Exact OAuth redirects | https://cms-3031023-xyz-www.ksloan90.workers.dev/admin/ and https://3031023.xyz/admin/ |
| Required status context | CMS content validation / cms-content-validation (pull_request) |
The pilot and permanent branch names are intentionally slash-free. Decap
CMS 3.15.1 inserts the configured Gitea branch directly into its tree-API URL;
a slash-named branch such as cms/pilot is split as an API path and its 404 is
presented as an empty collection. The permanent cms branch is still a
non-production staging branch: changes reach main only through the protected
pull-request flow.
The stable Worker alias and production admin route were confirmed. If either
origin changes, update repository configuration, CORS, and OAuth together
through review. Branch-specific preview aliases are intentionally not
registered and will return Unregistered Redirect URI during login.
Runner cache settings
The Dockerized runner uses a persistent /data volume and the same dedicated
Docker network as its job containers. Its non-confidential cache settings are:
cache:
enabled: true
dir: /data/cache
host: sloanops-gitea-runner
port: 8088
offline_mode: true
container:
network: sloanops-actions
offline_mode is safe here because every external uses: reference is pinned
to a reviewed full commit SHA. It prevents the runner from refreshing mutable
action branches on every job; npm packages remain keyed separately by
package-lock.json through setup-node's cache integration.
Applied CORS policy
The tracked Gitea service in
/home/sloan/gitRepos/SloanOps/infrastructure/docker-compose.yml was deployed
with exact origins for the internal CMS applications. The two origins relevant
to this repository are represented by these settings:
- GITEA__cors__ENABLED=true
- GITEA__cors__ALLOW_DOMAIN=https://cms-3031023-xyz-www.ksloan90.workers.dev,https://3031023.xyz
- GITEA__cors__METHODS=GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS
- GITEA__cors__MAX_AGE=10m
- GITEA__cors__ALLOW_CREDENTIALS=false
- GITEA__cors__HEADERS=Authorization,Content-Type
These are exact origins, not wildcards, and API requests do not carry cookies. Do not add arbitrary Worker preview hosts or localhost. Gitea's same-origin login page continues to use its own session cookie; Decap API calls use the OAuth bearer token.
After an approved Gitea deployment, verify a preflight without including a token or cookie:
curl --request OPTIONS 'https://git.sloanops.com/api/v1/version' \
--header 'Origin: https://cms-3031023-xyz-www.ksloan90.workers.dev' \
--header 'Access-Control-Request-Method: GET' \
--header 'Access-Control-Request-Headers: authorization,content-type'
The response must allow only the configured origin, requested method, and headers. Repeat with an unrelated origin and confirm that Gitea does not grant it cross-origin access. Also verify normal Git, web UI, API, and Actions behavior before continuing.
OAuth application
The Gitea OAuth2 application was created after the stable preview origin and CORS response were verified:
- Application name:
3031023.xyz Decap CMS. - Confidential client: unchecked. The browser client must use PKCE.
- Redirect URIs: exactly
https://cms-3031023-xyz-www.ksloan90.workers.dev/admin/andhttps://3031023.xyz/admin/, including trailing slashes. - Do not add wildcards, localhost, the journal hostname, or branch-specific Worker preview aliases as additional redirects.
- Copy only the public client ID into
backend.app_idinpublic/admin/config.ymlthrough a reviewed commit. - Do not store or use a client secret in the repository, Actions, Cloudflare, or Decap configuration.
The CMS account still needs repository write access; the branch rules below are the security boundary that reduces that write access to pilot content and media paths.
Applied branch protection rules
Create both rules before granting a CMS/client account write access or testing login. Gitea uses only the first matching rule, so the order is material.
Priority 1: production main
| Gitea control | Required value |
|---|---|
| Protected branch pattern | main |
| Priority | 1 (first/highest) |
| Push | Disabled for everyone; no push allowlist |
| Force push | Disabled |
| Required approvals | 1 |
| Approval allowlist | Enabled; only the named SloanOps operator user/team |
| Dismiss stale approvals | Enabled |
| Ignore stale approvals | Disabled |
| Block rejected reviews | Enabled |
| Block outstanding official review requests | Enabled |
| Block outdated pull requests | Enabled |
| Status checks | Enabled |
| Status check pattern | CMS content validation / cms-content-validation (pull_request) |
| Merge allowlist | Enabled; only the named SloanOps operator user/team |
| Administrators must follow protection | Enabled |
| Protection bypass allowlist | Disabled |
Leave both file-pattern fields empty on this rule. Direct pushes are already disabled, and executable or configuration changes must remain possible only in a separate operator-authored, reviewed pull request. The fallback rule prevents CMS users from placing those files on any source branch.
This rule means every main change arrives through a current pull request,
passes the one secret-free job that validates the CMS contract and builds and
verifies both sites, receives a current SloanOps approval, and is merged by a
SloanOps operator. A CMS/client account must not be added to the approval,
merge, administrator, or bypass allowlists.
Priority 2: all-branch fallback
| Gitea control | Required value |
|---|---|
| Protected branch pattern | * |
| Priority | 2 (after main) |
| Push | Disabled, except the unprotected file patterns below |
| Force push | Disabled |
| Unprotected file patterns | Exact semicolon-separated value below |
| Protected file patterns | Empty; every path omitted from the unprotected list remains protected |
| Protection bypass allowlist | Enabled only for the named SloanOps operator user/team |
| CMS/client account | Write access, but absent from every allowlist |
Do not assume a local Git identity can use the operator bypass. A direct
protected-file push was rejected during the final Journal repair sync, and the
successful path was a short branch followed by PR #11 into cms. Prefer that
reviewable path for all operator-authored CMS code and configuration changes.
Enter this exact single value in Unprotected file patterns:
sites/www/content/**;sites/blog/content/**;public/images/uploads/*.jpg;public/images/uploads/*.jpeg;public/images/uploads/*.png;public/images/uploads/*.webp;public/images/uploads/*.avif
Do not add a nested upload glob. The current Decap configuration writes media directly into the flat uploads directory, and the local verifier rejects nested media paths. Gitea file patterns are case-insensitive; CI verifies actual file signatures and a 25 MiB source safety ceiling before deployment. The build re-encodes the deployed copy to at most 1920 pixels and 500 KiB and strips embedded metadata.
Everything not listed is protected from CMS/client pushes on cms and
on every differently named branch, including:
.gitea/**
astro.config.mjs
package.json
package-lock.json
schemas/**
scripts/**
src/**
wrangler/**
sites.json
sites/*/site.json
public/admin/**
public/_headers
public/images/*
The final public/images/* line describes the effect of omission, not an
additional Gitea field value: existing approved images stay protected while
the explicitly listed public/images/uploads/*.<raster> paths remain writable.
Workflows, source, scripts, package files, schemas, manifests, Wrangler
configuration, site.json files, and the CMS application therefore cannot be
altered by choosing another branch name.
Routine verification and promotion
- Run
npm ci,npm run verify:cms, and both site build/verifier pairs. - For CMS application/configuration work, branch from current
cms, open a pull request back intocms, and review the no-index version previews. The required CMS status is scoped to pull requests intomain, so the local full check is mandatory before this staging merge. - Open
cmstomainand confirm Gitea reports the exact required status context above and blocks merge without its successful result and current SloanOps approval. - Merge deliberately, then confirm each affected production Worker workflow succeeds and smoke-test the public routes.
- Merge
mainback intocmsthrough another protected-branch pull request only when its tree contains file changes missing fromcms. Do not create a history-only sync when the trees already match. - Confirm previews remain no-index and never expand OAuth/CORS for a temporary branch alias.
Do not treat a Decap UI restriction as authorization. Gitea's first-match branch rules are the enforcement boundary, and the pull-request build is the content/media integrity boundary.