Files
3031023-xyz-public/docs/decap-cms-gitea-setup.md
T

8.8 KiB

Decap CMS: Gitea settings

Status: the preview-only pilot was accepted for promotion on 2026-09-04. The repository now targets the permanent cms staging branch. Update the exact CORS origin and PKCE OAuth redirect only after its first preview deployment confirms the predicted alias. Nothing has been merged to main.

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
Exact OAuth redirect URI https://cms-3031023-xyz-www.ksloan90.workers.dev/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 Workers alias was confirmed after the first approved preview deployment. If Cloudflare later assigns a different stable origin, update the repository configuration, CORS origin, and OAuth redirect URI together through review before enabling 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 contains these entries in the existing gitea.environment list and was deployed through the normal infrastructure change process:

- GITEA__cors__ENABLED=true
- GITEA__cors__ALLOW_DOMAIN=https://cms-3031023-xyz-www.ksloan90.workers.dev
- 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

This is one exact origin, no wildcard, no cookie-bearing cross-origin request, and only the API methods and non-safelisted request headers used by the Gitea backend. Do not add the production domains, the journal preview, arbitrary Workers 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 URI: exactly https://cms-3031023-xyz-www.ksloan90.workers.dev/admin/, including the trailing slash.
  • Do not add wildcards, localhost, either production hostname, or the journal preview 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

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 the 500 KiB limit before a preview can upload.

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.

Verification before an editor invitation

  1. Run npm ci, npm run verify:cms, and both site build/verifier pairs.
  2. Create cms from the reviewed baseline only after approval.
  3. Apply both protection rules together and confirm main is priority 1 and * is priority 2.
  4. Use the same non-operator account intended for Decap to prove allowed Markdown edits succeed and every protected-path/direct-main attempt fails.
  5. Open, but do not merge, a pull request into main. Confirm Gitea reports the exact required status context above and blocks merge without both its successful result and a current SloanOps approval.
  6. Confirm both branch previews remain no-index and production is unchanged.

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.