# Decap CMS: Gitea settings Status: SloanOps destination configuration prepared 2026-09-05; exact-origin CORS and OAuth replacement must accompany the protected CMS merge. Prior source-account settings were 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.sloanops.workers.dev` | | Production admin origin | `https://3031023.xyz` | | Exact OAuth redirects | `https://cms-3031023-xyz-www.sloanops.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: ```yaml 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: ```yaml - GITEA__cors__ENABLED=true - GITEA__cors__ALLOW_DOMAIN=https://cms-3031023-xyz-www.sloanops.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: ```bash curl --request OPTIONS 'https://git.sloanops.com/api/v1/version' \ --header 'Origin: https://cms-3031023-xyz-www.sloanops.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.sloanops.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**: ```text 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: ```text .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/*.` 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.