docs(guide): add KONTRA-GUIDE.md (editing/media/admin/ops); update archify+mermaid to git-files media (drop Garage)
This commit is contained in:
157
docs/KONTRA-GUIDE.md
Normal file
157
docs/KONTRA-GUIDE.md
Normal file
@@ -0,0 +1,157 @@
|
||||
# Kontra — Editing, Media, Admin & Ops
|
||||
|
||||
Single source of truth for working with the Kontra site. Covers: how content is
|
||||
structured, how to edit it (Obsidian or Decap), how media works, how the admin is
|
||||
configured, and how the whole thing runs + deploys.
|
||||
|
||||
**Live site:** https://kontra.lab.audasmedia.com.au
|
||||
**Admin (Decap):** https://admin.kontra.lab.audasmedia.com.au/web/admin/
|
||||
**Architecture map:** https://maps.lab.audasmedia.com.au/kontra_day/docs/kontra-architecture-map.html
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph editors["Content Editors"]
|
||||
O[Obsidian vault<br/>/obsidian/kontra_content] -->|git push| G
|
||||
N[Neovim / CLI] -->|git push| G
|
||||
D[Decap CMS<br/>admin.kontra.lab] -->|OAuth + git commit| G
|
||||
end
|
||||
subgraph gitea["Gitea (.35 Docker)"]
|
||||
G[(sam/kontra-content<br/>markdown + images = origin)]
|
||||
end
|
||||
subgraph prod["Kontra container (.13 :8600)"]
|
||||
P[git pull loop<br/>every 30s] -->|restart on change| B
|
||||
B[Go binary: templ + htmx + Goldmark]
|
||||
end
|
||||
subgraph media["Media (git files)"]
|
||||
F[(Images beside articles<br/>in the content repo)]
|
||||
end
|
||||
G -->|"SSH git"| P
|
||||
B -->|"{{media:...}} shortcode"| F
|
||||
B -->|"HTML"| CB[Caddy master (.35 :443)]
|
||||
CB --> WWW[browsers]
|
||||
D -->|login| G
|
||||
```
|
||||
|
||||
## 1. Content structure (the rules)
|
||||
|
||||
```
|
||||
content/
|
||||
├── config/site.yaml # site name, tagline, nav (lowercase keys)
|
||||
├── subjects/
|
||||
│ ├── world/
|
||||
│ │ ├── index.md # SUBJECT index — Decap folder marker (not an article)
|
||||
│ │ ├── _subject.yaml # subject config (name, template, color, ALL lowercase keys)
|
||||
│ │ ├── chinese-chips/
|
||||
│ │ │ ├── index.md # THE ARTICLE (file is always index.md inside its folder)
|
||||
│ │ │ ├── hero.jpg # images live beside their article
|
||||
│ │ │ └── figure-2.jpg
|
||||
│ │ └── quantum-ai/…
|
||||
│ └── tech/…
|
||||
├── pages/ # standalone pages (about, 404…)
|
||||
│ └── about.md
|
||||
└── _templates/
|
||||
└── article.md # Obsidian insert-template (⌘⇧T)
|
||||
```
|
||||
|
||||
**Hard rules**
|
||||
- Article file = **`index.md` inside its own folder** named exactly like the slug
|
||||
(`subjects/<subject>/<slug>/index.md`). The folder name = the URL slug (lowercased).
|
||||
- Filenames in a folder: **lowercase, no spaces** (`quantum-ai/`, not `Quantum-ai/`).
|
||||
- Images go in the **same folder as the article** — never the repo root, never a shared folder.
|
||||
- `_`-prefixed files are skipped by the site (`_subject.yaml`, `_templates/`, `.trash/`).
|
||||
|
||||
## 2. Editing content
|
||||
|
||||
### A. Obsidian (recommended daily editor)
|
||||
Open **`/home/sam/obsidian/kontra_content`** (a symlink to the content repo) as a vault.
|
||||
The **GitHub Sync** plugin auto-pushes on save (~15s).
|
||||
|
||||
- **New article:** insert the template via **⌘⇧T → "article"** — this substitutes `{{title}}`/`{{date}}`.
|
||||
(If you hand-copy the template file, those variables stay literal and break front-matter.)
|
||||
- **New subject:** create a folder under `subjects/` with an `index.md` (used as the subject index)
|
||||
and `_subject.yaml`.
|
||||
- **Publish/hide:** `published: true` → live; `published: false` → hidden everywhere (404 + removed from all lists). Omit = published.
|
||||
- **Featured:** `featured: true` puts it in the hero lead slot.
|
||||
|
||||
### B. Decap (browser admin)
|
||||
Login with **Gitea** → you get a nested tree: `Articles → world → chinese-chips …`.
|
||||
Click an article to edit. Same file → same result. **No manual push needed** — Decap commits to Gitea, autosync publishes.
|
||||
|
||||
## 3. Media (images)
|
||||
|
||||
Images are **git files beside their article** — no S3/object store.
|
||||
|
||||
```
|
||||
subjects/world/chinese-chips/
|
||||
index.md
|
||||
hero.jpg ← dragged into Obsidian (or placed in the folder)
|
||||
```
|
||||
|
||||
**Referencing**
|
||||
- Body: ``
|
||||
- Front-matter hero: `image: "{{media:chinese-chips/hero.jpg}}"` ← **must be quoted**
|
||||
- Rule: `{{media:<slug>/<exact-filename>}}` — slug lowercase, **exact filename incl. extension** (`.jpg` ≠ `.jpeg`), **no leading slash**, **no backticks** around a real image markdown line.
|
||||
|
||||
The app serves `/media/<path>` by mapping the slug→subject folder (safe-path guarded).
|
||||
|
||||
## 4. The admin (Decap) config
|
||||
|
||||
- Lives at **`app/src/web/admin/config.yml`** (embedded into the binary at build).
|
||||
- **Nested collections** — this is how it lists the 2-level tree. Do NOT "simplify" it:
|
||||
```yaml
|
||||
collections:
|
||||
- name: articles
|
||||
label: Articles
|
||||
folder: subjects
|
||||
create: true
|
||||
slug: "{{slug}}"
|
||||
nested: { depth: 100, subfolders: false } # subfolders:false is REQUIRED
|
||||
meta: { path: { widget: string, label: 'Path', index_file: 'index' } }
|
||||
```
|
||||
- **Decap gotchas (do not repeat):**
|
||||
- `index_file: 'index'` (not `_index`), `subfolders: false` (not true)
|
||||
- Labels in single quotes: NO apostrophes inside (breaks YAML — use double quotes)
|
||||
- The media library is disabled (`media_folder: ""`) — images are git files, not uploaded through Decap.
|
||||
- `subjects` + `pages` collections also exist. Auth is **Gitea OAuth** (app `decap-kontra`, PKCE).
|
||||
|
||||
## 5. Ops — how it runs
|
||||
|
||||
**Machines**
|
||||
| Machine | IP | Role |
|
||||
|---|---|---|
|
||||
| Gitea (origin) | .35 :3001 | `sam/kontra-content` (content+images), `sam/kontra` (app) |
|
||||
| Kontra container | .13 :8600 | Go binary; pulls content every 30s + restarts on change |
|
||||
| Caddy master | .35 :443 | TLS; `kontra.lab…` + `admin.kontra…` → `.13:8600` |
|
||||
| Dev (.27) | .27 | Obsidian vault + app source + build |
|
||||
|
||||
**Build** (after source changes, on .27)
|
||||
```bash
|
||||
cd app && ./build.sh # templ generate → go build → app/kontra-bin
|
||||
```
|
||||
The container builds inside Docker (golang stage) — update `config.yml`/assets then redeploy.
|
||||
|
||||
**Deploy to .13** (the container runs from `/home/sam/Docker/Containers/kontra/`)
|
||||
```bash
|
||||
# source (needed bits)
|
||||
scp app/go.mod app/go.sum sam@192.168.20.13:/home/sam/Docker/Containers/kontra/src/
|
||||
rsync -az --delete --exclude bin --exclude kontra-bin --exclude .git \
|
||||
app/src/ sam@192.168.20.13:/home/sam/Docker/Containers/kontra/src/src/
|
||||
ssh sam@192.168.20.13 'cd /home/sam/Docker/Containers/kontra && docker compose up -d --build'
|
||||
```
|
||||
> `.go.sum` must move with `go.mod`; and use `--delete` or stale files (like old `media.go`) break the build.
|
||||
|
||||
**Content autosync** — pushes to `sam/kontra-content` are pulled by the container every 30s; server restarts on change. No CI/CD needed.
|
||||
|
||||
**Secrets** — the Gitea deploy token lives in `.env` (repo root, **gitignored**, chmod 600). Never commit.
|
||||
|
||||
## 6. Backups
|
||||
- Content + images are in the git repo → backed up by Gitea (mirrors + clients) and the production clone on `.13`.
|
||||
- The app source lives in git on Gitea.
|
||||
- House-wide offsite backup covers these hosts per the Backup Architecture plan.
|
||||
|
||||
## 7. For AI agents resuming this project
|
||||
1. Read this file first.
|
||||
2. The site is **live** and the pipeline works — prefer the smallest change + `rsync --delete` deploy + verify on `https://kontra.lab.audasmedia.com.au`.
|
||||
3. Decap config: treat `config.yml` as sacred (nested `subfolders:false` + `index_file:index`).
|
||||
4. Media = git files. `{{media:<slug>/<file>}}`, quoted in front-matter, exact filename.
|
||||
5. Don't reintroduce S3/Garage for media (dropped 2026-09-10).
|
||||
Reference in New Issue
Block a user