initial scaffold: emdash catalog, helm chart, woodpecker pipeline, ddev
- app/: Emdash scaffold (Astro 6, node target) with cmses/plugins/pages collections - app/seed/seed.json: WordPress→Emdash parity for kotkanagrilli.fi (~30 entries) - Dockerfile + docker/entrypoint.sh: multi-stage build, single PVC at /app/state - deploy/helm/: chart mirroring emdash-kotkanagrilli (single-replica, sqlite, kotkan) - deploy/fleet-overlay/: HelmRelease/source/image-automation templates for anton-helm-workloads (staging + production) - .woodpecker/container.yaml: arm64 build, three OCI tags per push (immutable 0.1.<pipeline> + floating <branch> + <branch>-latest) - .ddev/: local dev with nginx proxy to emdash on :4321 - README/DEPLOYMENT/ARCHITECTURE/CLAUDE: docs covering the three-repo pipeline (cms-plugins + anton-helm-workloads + Gitea OCI registry)
This commit is contained in:
+128
@@ -0,0 +1,128 @@
|
||||
# Architecture
|
||||
|
||||
This file is the contract between the build (Dockerfile, Woodpecker, Helm chart)
|
||||
and the FluxCD reconciliation in `anton-helm-workloads`. See `DEPLOYMENT.md` for
|
||||
the operational walkthrough.
|
||||
|
||||
## Repo shape
|
||||
|
||||
```
|
||||
cms-plugins/
|
||||
├── app/ ← Emdash/Astro source
|
||||
│ ├── astro.config.mjs ← node target, sqlite() + local()
|
||||
│ ├── package.json
|
||||
│ ├── src/ ← pages, layouts, components
|
||||
│ └── seed/seed.json ← collections + kotkanagrilli plugin entries
|
||||
├── Dockerfile ← multi-stage build, single PVC at /app/state
|
||||
├── docker/entrypoint.sh ← runs `emdash init` on first boot
|
||||
├── deploy/
|
||||
│ ├── helm/ ← chart consumed by Flux directly from this repo
|
||||
│ └── fleet-overlay/ ← HelmRelease/source/image-automation templates
|
||||
│ ├── cms-plugins-staging/ ← copy → anton-helm-workloads/cms-plugins-staging/
|
||||
│ └── cms-plugins-production/ ← copy → anton-helm-workloads/cms-plugins-production/
|
||||
├── .woodpecker/container.yaml ← build pipeline
|
||||
└── .ddev/ ← local dev (DDEV nginx → emdash service on :4321)
|
||||
```
|
||||
|
||||
## Build pipeline
|
||||
|
||||
A push to `develop` / `staging` / `production` runs
|
||||
`.woodpecker/container.yaml`. The pipeline labels itself `arch: arm64`
|
||||
because the deploy target (`kotkan`) is an arm64 node, so building
|
||||
natively avoids a cross-compile step.
|
||||
|
||||
For each build the pipeline pushes three OCI tags to
|
||||
`git.oleks.space/oleks/cms-plugins`:
|
||||
|
||||
| Tag | Mutability | Purpose |
|
||||
|---|---|---|
|
||||
| `0.1.<pipeline>` | Immutable | Audit trail. Use as a rollback target. |
|
||||
| `<branch>` | Floating | Watched by FluxCD's `ImagePolicy`. Retagged on every build. |
|
||||
| `<branch>-latest` | Floating | Cosmetic. Resolves the chart's `image.tag` fallback to a real ref when the digest path is unset. |
|
||||
|
||||
Only the `staging` and `production` branches have an `ImagePolicy`, so
|
||||
only those move pods.
|
||||
|
||||
## Container shape
|
||||
|
||||
Multi-stage Dockerfile:
|
||||
|
||||
- `deps` — `node:22-bookworm-slim` + `python3 make g++` (better-sqlite3
|
||||
build deps). Falls back to `npm install` if there's no committed lockfile.
|
||||
- `build` — `npm run build`.
|
||||
- `runtime` — `node:22-bookworm-slim` + `tini`. Runs as uid 1001. Persistent
|
||||
state symlinked from `/app/data.db` and `/app/uploads` → `/app/state/...`
|
||||
so the one PVC mounted at `/app/state` covers both SQLite and uploaded
|
||||
media.
|
||||
|
||||
Entrypoint runs `emdash init` on first boot (idempotent), then `exec`s
|
||||
the Astro node server on port 4321.
|
||||
|
||||
## Helm chart
|
||||
|
||||
`deploy/helm/` ships with the app. The chart is **not** packaged or pushed
|
||||
to an OCI registry — FluxCD's `GitRepository` source pulls it directly from
|
||||
the matching branch of this repo. The `ignore` rule in `source.yaml`
|
||||
restricts reconciliation to `/deploy/helm`, so app-source pushes don't
|
||||
trigger chart re-reconcile.
|
||||
|
||||
Per-env values overlay `values.yaml` via `values-staging.yaml` /
|
||||
`values-production.yaml` (for `helm upgrade` direct use) and via the
|
||||
`values:` block in the FluxCD `HelmRelease`.
|
||||
|
||||
Key shape decisions:
|
||||
|
||||
- **`replicas: 1`, `strategy: Recreate`.** SQLite is single-writer.
|
||||
- **`nodeSelector: kotkan`.** `local-path` PV is sticky to one node; emdash
|
||||
is colocated with the legacy kotkanagrilli WP install.
|
||||
- **Pinned by digest, not tag.** The HelmRelease sets
|
||||
`values.image.digest: "<sha256:...>" # {"$imagepolicy": ...}` so that
|
||||
`helm upgrade` detects a change when the floating tag is reassigned.
|
||||
Without digest pinning, `staging` stays the literal string `"staging"`
|
||||
through every build and Helm sees no spec change.
|
||||
- **Single PVC, `helm.sh/resource-policy: keep`.** Losing the PVC means
|
||||
losing all CMS data — uninstalls do not delete it.
|
||||
|
||||
## Flux contract
|
||||
|
||||
Two repos cooperate:
|
||||
|
||||
- **cms-plugins** (this one) exposes the chart at `deploy/helm/` on each
|
||||
branch.
|
||||
- **anton-helm-workloads** runs the show: `GitRepository` points at the
|
||||
matching branch of cms-plugins, `HelmRelease` references
|
||||
`chart: ./deploy/helm`, `ImagePolicy` + `ImageUpdateAutomation` watch
|
||||
the floating tag and rewrite the digest setter back into
|
||||
`helmrelease.yaml`.
|
||||
|
||||
For the workloads repo to write digest commits, a `GitRepository` named
|
||||
`anton-workloads-image-automation` in `flux-system` namespace must exist
|
||||
with write access to `anton-helm-workloads:main`. The emdash-kotkanagrilli
|
||||
analogue is `oleks-fleet-image-automation` in the personal fleet repo.
|
||||
|
||||
## Data model
|
||||
|
||||
Three Emdash collections (`app/seed/seed.json`):
|
||||
|
||||
- **cmses** — CMS platforms. Seeded with WordPress + Emdash.
|
||||
- **plugins** — one entry per plugin. Fields: title, purpose, source_cms,
|
||||
target_cms, category, status (`port`/`built-in`/`saas`/`drop`/`gated`/`done`/`proposed`),
|
||||
source_repo_url, target_repo_url, notes (portableText).
|
||||
- **pages** — static content (About, Submit, etc.).
|
||||
|
||||
The seeded plugin entries come from
|
||||
`~/projects/emdash.kotkanagrilli.fi/docs/parity.md` and the
|
||||
`mu-plugins/` / `plugins-custom/` directories in
|
||||
`~/projects/kotkanagrilli.fi/`. New entries are added through the Emdash
|
||||
admin (`/_emdash/admin`).
|
||||
|
||||
## What this does NOT solve
|
||||
|
||||
- **Cloudflare Workers target.** The kotkanagrilli sister project has
|
||||
Cloudflare boundary files (`src/worker.ts`, `wrangler.jsonc`) for a
|
||||
later flip; this repo skips them. They can be added back from the
|
||||
emdash-kotkanagrilli pattern when needed.
|
||||
- **Authentication on the admin.** Emdash's default passkey auth runs;
|
||||
the first visit to `/_emdash/admin` redirects to `/setup` and creates
|
||||
the first admin.
|
||||
- **Multi-language.** No i18n — the catalog is English-only by default.
|
||||
Reference in New Issue
Block a user