Files
3031023-xyz-public/docs/decap-cms-gitea-setup.md
T
2026-09-04 22:30:00 -04:00

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/ and https://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_id in public/admin/config.yml through 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

  1. Run npm ci, npm run verify:cms, and both site build/verifier pairs.
  2. For CMS application/configuration work, branch from current cms, open a pull request back into cms, and review the no-index version previews. The required CMS status is scoped to pull requests into main, so the local full check is mandatory before this staging merge.
  3. Open cms to main and confirm Gitea reports the exact required status context above and blocks merge without its successful result and current SloanOps approval.
  4. Merge deliberately, then confirm each affected production Worker workflow succeeds and smoke-test the public routes.
  5. Merge main back into cms through another protected-branch pull request only when its tree contains file changes missing from cms. Do not create a history-only sync when the trees already match.
  6. 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.