157 lines
7.2 KiB
Markdown
157 lines
7.2 KiB
Markdown
# 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). |