Document Decap CMS demo pilot
This commit is contained in:
@@ -0,0 +1,289 @@
|
||||
# Decap CMS Gitea pilot
|
||||
|
||||
Status: planned; no CMS, OAuth application, Gitea CORS change, branch
|
||||
protection, or deployment has been enabled by this document.
|
||||
|
||||
Updated: 2026-09-03
|
||||
|
||||
## Purpose
|
||||
|
||||
Use the `3031023.xyz` demonstration repository to determine whether
|
||||
Decap CMS can provide a useful editing interface for all sites owned under one
|
||||
root domain without allowing an editor to publish directly to production or
|
||||
modify deployment code.
|
||||
|
||||
The pilot covers both the primary Happy Tails site and its journal subdomain.
|
||||
That matches the SloanOps repository boundary: one private repository per root
|
||||
domain, with independently built sites beneath `sites/`. A future client who
|
||||
owns the root-domain site also owns the hosted subdomains represented in that
|
||||
repository.
|
||||
|
||||
This plan does not authorize a production merge, domain change, contact-form
|
||||
activation, repository invitation, or removal of a rollback resource.
|
||||
|
||||
## Current evidence
|
||||
|
||||
- `3031023.xyz` and `blog.3031023.xyz` build independently from this
|
||||
repository and deploy through separate Workers Static Assets workflows.
|
||||
- A push outside `main` already uploads no-index primary and/or blog preview
|
||||
versions according to the paths changed.
|
||||
- Blog posts, FAQs, services, and team members are Markdown collections with
|
||||
Astro/Zod validation and are suitable for a bounded Decap pilot.
|
||||
- The live Gitea instance reported version `1.27.3` during the Phase 9 review.
|
||||
Decap's Gitea backend requires Gitea 1.20 or newer.
|
||||
- A preflight request from the proposed CMS origin currently returns `405`
|
||||
without an access-control origin header. Gitea CORS must be configured before
|
||||
browser-based Decap authentication or repository access will work.
|
||||
- Decap authenticates to Gitea with a non-confidential PKCE OAuth application.
|
||||
Its Gitea backend currently has no editorial pull-request workflow, so it
|
||||
writes directly to the configured branch.
|
||||
- The Gitea backend does not support Git LFS. Pilot media must remain small,
|
||||
ordinary repository assets.
|
||||
|
||||
References:
|
||||
|
||||
- <https://decapcms.org/docs/gitea-backend/>
|
||||
- <https://decapcms.org/docs/configure-decap-cms/>
|
||||
- <https://decapcms.org/docs/widgets/image/>
|
||||
- <https://docs.gitea.com/development/oauth2-provider/>
|
||||
- <https://docs.gitea.com/usage/access-control/protected-branches/>
|
||||
- <https://github.com/decaporg/decap-cms/issues/7115>
|
||||
|
||||
## Proposed pilot architecture
|
||||
|
||||
```text
|
||||
Decap at /admin/
|
||||
|
|
||||
| Gitea PKCE OAuth and content/media commits only
|
||||
v
|
||||
cms/pilot branch
|
||||
|
|
||||
+--> primary no-index Worker preview
|
||||
|
|
||||
+--> blog no-index Worker preview
|
||||
|
|
||||
v
|
||||
manual Gitea pull request to protected main
|
||||
|
|
||||
v
|
||||
required validation + SloanOps approval
|
||||
|
|
||||
v
|
||||
production merge and deploy only with separate approval
|
||||
```
|
||||
|
||||
The shared `public/` directory may serve `/admin/` in both preview builds. That
|
||||
is acceptable because both sites belong to the same root-domain repository and
|
||||
the same owner. One Decap configuration exposes clearly labeled Primary Site
|
||||
and Journal collection groups.
|
||||
|
||||
Expected preview URLs for branch `cms/pilot`:
|
||||
|
||||
- `https://cms-pilot-3031023-xyz-www.ksloan90.workers.dev/admin/`
|
||||
- `https://cms-pilot-3031023-xyz-www.ksloan90.workers.dev/`
|
||||
- `https://cms-pilot-3031023-xyz-blog.ksloan90.workers.dev/`
|
||||
|
||||
Confirm the aliases from the successful Gitea Actions run rather than treating
|
||||
these predicted URLs as authoritative.
|
||||
|
||||
## Initial Decap scope
|
||||
|
||||
Allow editing and creation only within these collections:
|
||||
|
||||
| CMS group | Repository path | Initial capability |
|
||||
| --- | --- | --- |
|
||||
| Primary site FAQs | `sites/www/content/faq/**` | Create, edit, draft, reorder, delete after confirmation |
|
||||
| Primary services | `sites/www/content/services/**` | Create, edit, draft, reorder, feature |
|
||||
| Primary team | `sites/www/content/team/**` | Create, edit, draft, reorder, select an approved image |
|
||||
| Journal posts | `sites/blog/content/posts/**` | Create, edit, draft, date, tag, update |
|
||||
| Shared pilot media | `public/images/uploads/**` | Upload bounded raster images after text editing is proven |
|
||||
|
||||
Do not expose `sites.json`, either `site.json`, schemas, workflows, Wrangler
|
||||
configuration, package files, source components, scripts, contact routing, or
|
||||
analytics through the CMS during this pilot.
|
||||
|
||||
Configure the default media library with a conservative file-size limit. Do
|
||||
not accept executables, archives, HTML, scripts, or SVG uploads. Asset rights,
|
||||
alt text, crop, dimensions, and optimization remain operator review items even
|
||||
when the CMS accepts an upload.
|
||||
|
||||
## Repository protection model
|
||||
|
||||
### `main`
|
||||
|
||||
- Disable direct pushes for CMS/client users.
|
||||
- Require a pull request.
|
||||
- Require at least one SloanOps approval.
|
||||
- Require the dedicated CMS content validation status.
|
||||
- Block merging an outdated or rejected pull request.
|
||||
- Permit merges only by the SloanOps operator allowlist.
|
||||
- Keep force-push disabled.
|
||||
|
||||
### Fallback rule for non-production branches
|
||||
|
||||
Create a branch-protection fallback covering every branch, with the specific
|
||||
`main` rule at higher priority. CMS/client users may push only the following
|
||||
unprotected 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
|
||||
```
|
||||
|
||||
Add the equivalent nested-path globs if the CMS is allowed to create media
|
||||
subdirectories. Listing raster extensions explicitly prevents an editor from
|
||||
using the media directory as a general unprotected file path.
|
||||
|
||||
All other paths remain protected, including:
|
||||
|
||||
```text
|
||||
.gitea/**
|
||||
astro.config.mjs
|
||||
package.json
|
||||
package-lock.json
|
||||
schemas/**
|
||||
scripts/**
|
||||
src/**
|
||||
wrangler/**
|
||||
sites.json
|
||||
sites/*/site.json
|
||||
public/admin/**
|
||||
```
|
||||
|
||||
SloanOps operator accounts may use the branch-rule bypass allowlist for
|
||||
reviewed development. Verify the restrictions through the same Gitea account
|
||||
used by Decap; a UI-only field restriction is not a repository security
|
||||
boundary.
|
||||
|
||||
The protected-path fallback matters even though `main` requires a pull
|
||||
request. Preview workflows inherit Cloudflare credentials. An editor must not
|
||||
be able to create another branch, alter a workflow or executable build file,
|
||||
and run unreviewed code with those credentials.
|
||||
|
||||
## Pull-request validation
|
||||
|
||||
Add a secret-free validation workflow for pull requests into `main`. It should
|
||||
always:
|
||||
|
||||
1. install locked dependencies;
|
||||
2. build and verify the primary site;
|
||||
3. build and verify the blog;
|
||||
4. reject content that fails the Astro/Zod schema; and
|
||||
5. report one stable required status context.
|
||||
|
||||
Preview deployment remains branch-specific and may use the existing Worker
|
||||
workflows. Cloudflare credentials stay scoped to the deployment step; content
|
||||
validation does not need them.
|
||||
|
||||
Because Decap writes to a fixed `cms/pilot` branch, SloanOps creates the pull
|
||||
request in Gitea. After an approved merge, synchronize the CMS branch with
|
||||
`main` before the next editing cycle. Do not automate production merge.
|
||||
|
||||
## Gitea and Decap setup
|
||||
|
||||
1. Add a non-confidential Gitea OAuth application for the exact preview
|
||||
`/admin/` redirect URI or URIs. Store only the public client ID in Decap.
|
||||
2. Enable Gitea API CORS for the exact approved preview origin or origins.
|
||||
Allow only required methods and headers, including `Authorization` and
|
||||
`Content-Type`; do not use a wildcard origin.
|
||||
3. Create `cms/pilot` from the reviewed repository baseline.
|
||||
4. Add pinned Decap admin assets beneath `public/admin/`. Do not load a floating
|
||||
major-version range from a CDN.
|
||||
5. Configure the Gitea backend for `SloanOps/3031023-xyz` and branch
|
||||
`cms/pilot`.
|
||||
6. Model every persisted frontmatter field with limits matching
|
||||
`src/content.config.ts`. Do not let a CMS save silently remove an existing
|
||||
field.
|
||||
7. Apply and verify the `main` and fallback branch protections before inviting
|
||||
or testing a non-operator editor.
|
||||
8. Add the secret-free pull-request validation workflow.
|
||||
9. Push only the review branch and confirm both no-index preview builds before
|
||||
any CMS login test.
|
||||
|
||||
## Test matrix
|
||||
|
||||
Record the Gitea execution, commit, and preview identifiers for each test. Do
|
||||
not record OAuth tokens or Cloudflare credentials.
|
||||
|
||||
| ID | Test | Expected result |
|
||||
| --- | --- | --- |
|
||||
| DC-01 | Build both sites before CMS changes | Current build and verifier suites pass. |
|
||||
| DC-02 | Open each preview `/admin/` route | Admin shell loads and remains no-index. |
|
||||
| DC-03 | Authenticate with the approved operator account | PKCE login succeeds and the configured collections load. |
|
||||
| DC-04 | Authenticate with an account without repository write access | Repository editing is unavailable. |
|
||||
| DC-05 | Edit an existing draft journal post | One content-only commit lands on `cms/pilot`; the blog preview rebuilds. |
|
||||
| DC-06 | Create, edit, and delete a disposable journal post | Filename, dates, tags, body, draft state, and cleanup remain schema-valid. |
|
||||
| DC-07 | Edit a primary FAQ | Only the primary preview rebuilds and displays the change. |
|
||||
| DC-08 | Edit service ordering and featured state | YAML types remain correct and the primary verifier passes. |
|
||||
| DC-09 | Edit a team entry without changing its image | Existing fields and image path are preserved exactly. |
|
||||
| DC-10 | Upload a bounded raster image | File lands only in the approved media path and renders on the relevant preview. |
|
||||
| DC-11 | Attempt an oversized upload and a disallowed file type | The CMS size limit rejects the oversized file before upload; Gitea's unprotected-extension patterns reject the disallowed path. |
|
||||
| DC-12 | Attempt to modify `.gitea/workflows/**`, source, package, script, schema, Wrangler, and site-manifest paths with the CMS account | Gitea rejects every push. |
|
||||
| DC-13 | Attempt to create a differently named branch and change a protected file | The fallback rule rejects the push; branch naming cannot bypass protection. |
|
||||
| DC-14 | Attempt a direct push to `main` | Gitea rejects it. |
|
||||
| DC-15 | Open a pull request from `cms/pilot` to `main` | Both sites validate; required review and merge controls are present. |
|
||||
| DC-16 | Attempt to merge without status or approval | Gitea blocks the merge. |
|
||||
| DC-17 | Inspect both preview responses | Expected routes work; preview metadata/headers prevent indexing; production remains unchanged. |
|
||||
| DC-18 | Revert the disposable CMS changes on `cms/pilot` | Preview returns to baseline without changing either public site. |
|
||||
| DC-19 | Refresh, sign out, reauthenticate, and let a session expire | Session behavior is understandable and no unsaved-content loss is observed. |
|
||||
| DC-20 | Review commit attribution and audit history | Gitea identifies the editor and the exact content/media changes. |
|
||||
|
||||
Do not merge DC-15 during the initial infrastructure and editing test. A later
|
||||
harmless demo-content merge requires explicit approval and repeats both public
|
||||
site checks after deployment.
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
Accept the pilot only when:
|
||||
|
||||
- both site families are understandable in one CMS;
|
||||
- content edits preserve every modeled field and pass Astro validation;
|
||||
- previews update reliably without moving production traffic;
|
||||
- direct `main` pushes and protected-path changes fail for the CMS account;
|
||||
- PR validation and operator approval are mandatory;
|
||||
- media limits and repository growth remain acceptable;
|
||||
- login/session behavior is usable; and
|
||||
- rollback requires no production-domain or DNS change.
|
||||
|
||||
Stop and redesign if the Gitea backend cannot honor the branch/file
|
||||
protections, OAuth grants materially broader access than the accepted account
|
||||
role, content serialization loses fields, or previews require granting the CMS
|
||||
account control over executable deployment files.
|
||||
|
||||
## Rollback and cleanup
|
||||
|
||||
If the pilot is rejected or paused:
|
||||
|
||||
1. Revert or delete disposable changes on `cms/pilot`.
|
||||
2. Delete the two disposable preview aliases/versions if desired; do not delete
|
||||
production Workers or retained legacy rollback resources.
|
||||
3. Revoke/delete the Decap OAuth application.
|
||||
4. Remove the pilot CORS origins and redeploy Gitea after verifying normal Git,
|
||||
UI, API, and Actions behavior.
|
||||
5. Remove `/admin/` only through a reviewed commit.
|
||||
6. Remove or retain branch protections deliberately; never weaken `main` as
|
||||
cleanup.
|
||||
|
||||
## Implementation estimate
|
||||
|
||||
Expected hands-on implementation time: **6–9 hours**, normally one focused
|
||||
working day. Budget up to two elapsed days if OAuth/CORS behavior or Gitea
|
||||
protection semantics require troubleshooting.
|
||||
|
||||
| Work | Estimate |
|
||||
| --- | ---: |
|
||||
| Decap admin shell, pinned dependency, and Gitea configuration | 1–1.5 hours |
|
||||
| Model blog, FAQ, services, and team collections | 1.5–2 hours |
|
||||
| Add secret-free PR validation and preview links/instructions | 1–1.5 hours |
|
||||
| Configure CORS, OAuth, CMS branch, and protection rules | 1–1.5 hours |
|
||||
| Run the authentication, editing, protection, preview, and rollback matrix | 1.5–2.5 hours |
|
||||
| Record results and resolve small integration defects | 0.5 hour |
|
||||
|
||||
This estimate covers the internal demo pilot, not a polished client-editable
|
||||
service. Client rollout still needs observed usability, account invitation and
|
||||
offboarding procedures, support scope, media policy, and pricing.
|
||||
Reference in New Issue
Block a user