diff --git a/200 projects/240 Web Sites/Kontra Day.md b/200 projects/240 Web Sites/Kontra Day.md
index 95cb852..4db49b3 100644
--- a/200 projects/240 Web Sites/Kontra Day.md
+++ b/200 projects/240 Web Sites/Kontra Day.md
@@ -1,6 +1,6 @@
---
created: 2026-09-09 11:54
-modified: 2026-09-09 12:05
+modified: 2026-09-10 16:30
type: note
tags:
- website
@@ -10,40 +10,26 @@ tags:
- goth
- golang
- self-hosted
+ - editing
+ - media
+ - admin
+ - ops
aliases:
- Kontra
- Kontra Day
---
-# Kontra
+# Kontra β Editing, Media, Admin & Ops
-> **Kontra** is a self-hosted news-opinion site engine. Content is plain Markdown in a git repo;
-> a single static Go binary renders it into a fast, SEO-friendly editorial site; a Decap CMS admin
-> (authenticated with Gitea OAuth) lets editors work in the browser; and a git-driven pull loop
-> publishes changes live within ~30 seconds of a save.
+> **Kontra** is a self-hosted news-opinion site engine. Content + images are plain files in a git
+> repo; a single static Go binary renders them into an SEO-friendly editorial site. Editors work in
+> **Obsidian** (daily) or **Decap** (browser). No database, no S3/object store.
-**Live:** https://kontra.lab.audasmedia.com.au Β· **Admin:** https://admin.kontra.lab.audasmedia.com.au/web/admin/
+**Live:** 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
**Repo (public):** https://gitea.lab.audasmedia.com.au/sam/kontra
-> πΊ **Interactive architecture map:** https://maps.lab.audasmedia.com.au/kontra_day/docs/kontra-architecture-map.html
-> (pan/zoom, search, route tracing, dark/light themes)
-
----
-
-## What it is
-
-A complete, self-hosted news/opinion publishing platform designed to run on the home lab:
-
-- **Markdown-first content** β articles are `.md` files with YAML front-matter (`title`, `author`,
- `date`, `kicker`, `subject`, `template`, `featured`, β¦). No database to manage.
-- **One static binary** β Go (golang) + templ + htmx + design-token CSS. No runtime, no Node, no PHP.
-- **Three edit paths** β Obsidian vault, Neovim/CLI, or the Decap CMS browser editor. All converge on one git repo.
-- **Git is the API** β full history, rollback, branch review, backups included.
-- **Autopublish** β `git push` β container pulls every 30s β live. Proven for add + delete.
-- **Self-hosted everything** β Gitea (origin), Garage S3 (media), Caddy (TLS), Decap (admin), Docker (runtime).
-
-## Architecture (Mermaid)
-
```mermaid
flowchart LR
subgraph editors["Content Editors"]
@@ -51,132 +37,100 @@ flowchart LR
N[Neovim / CLI] -->|git push| G
D[Decap CMS
admin.kontra.lab] -->|OAuth + git commit| G
end
-
- subgraph gitea["Gitea (192.168.20.35 :3001 Docker)"]
- G[(sam/kontra-content
markdown origin of truth)]
+ subgraph gitea["Gitea (.35)"]
+ G[(sam/kontra-content
md + images = origin)]
end
-
- subgraph prod["Kontra container (192.168.20.13 :8600 Docker)"]
- P[git pull loop
every 30s] -->|restart on change| B
- B[Go binary
templ + htmx + Goldmark]
- B --> MD[(Markdown render
front-matter + shortcodes)]
+ subgraph prod["Kontra container (.13 :8600)"]
+ P[git pull 30s] -->|restart on change| B
+ B[Go: templ+htmx+Goldmark]
end
-
- subgraph media["Media (Garage S3 on .13)"]
- S3[(Garage S3
kontra-day bucket)]
+ subgraph media["Media (git files)"]
+ F[(images beside articles)]
end
-
- G -->|"SSH git"| P
- B -->|"shortcode: media"| S3
- B -->|"emits HTML"| CB[Caddy master
.35 :80/:443]
- CB -->|"reverse_proxy :8600"| B
-
- subgraph net["Network"]
- CB -->|https| WWW[kontra.lab.audasmedia.com.au]
- CB -->|https| ADM[admin.kontra.lab.audasmedia.com.au]
- end
-
- WWW -->|browser| B
- ADM -->|browser| D
+ G -->|SSH| P
+ B -->|"{{media:slug/file}}"| F
+ B -->|HTML| CB[Caddy .35 :443] --> WWW[browsers]
```
-**Interactive version:** [maps.lab](https://maps.lab.audasmedia.com.au/kontra_day/docs/kontra-architecture-map.html) Β·
-Rendered PNG: `docs/diagrams/kontra-architecture.png` Β· Mermaid source: `docs/kontra-architecture.mmd`
-
---
-## How to use it
+## 1. Content structure (rules)
-### As an editor
-1. Open **https://admin.kontra.lab.audasmedia.com.au/web/admin/** β **Login with Gitea** (your Gitea account, needs push access to `sam/kontra-content`).
-2. **Articles** collection: create/edit, set subject/kicker/excerpt/featured, write Markdown body, Save.
-3. Wait ~30 s β the site updates itself. No deploy step.
-
-Or edit the files directly:
-- **Obsidian vault:** `/home/sam/obsidian/kontra_content` (symlink β content repo). Open as a vault, edit markdown, the Obsidian Git plugin pushes.
-- **Neovim/CLI:** clone `ssh://git@gitea.lab.audasmedia.com.au:2222/sam/kontra-content.git`, edit, `git push`.
-
-### Content model
```
content/
-βββ config/site.yaml # site name, tagline, nav order
-βββ subjects//
-β βββ _subject.yaml # name, template, accent color
-β βββ .md # article (front matter + markdown)
-βββ pages/.md # standalone pages
+βββ config/site.yaml
+βββ subjects/
+β βββ world/
+β βββ index.md # SUBJECT index (Decap folder marker, not an article)
+β βββ _subject.yaml
+β βββ chinese-chips/
+β β βββ index.md # THE ARTICLE β file is always index.md
+β β βββ hero.jpg # images beside their article
+β βββ quantum-ai/
+βββ pages/about.md
+βββ _templates/article.md
```
-Template resolution: page front-matter β subject `_subject.yaml` β site config β theme fallback.
-Media: `{{media:file.jpg}}` (β Garage S3 bucket), `{{embed:vimeo:ID}}` (β video embed).
-### As an operator
-- **Restart/redeploy:** `ssh sam@192.168.20.13` β `cd /home/sam/Docker/Containers/kontra && docker compose up -d --build`
-- **Pull loop** lives in `entrypoint.sh` inside the container (git pull every 30s, restart on change).
-- **Live site logs:** `docker logs -f kontra`
-- **Content repo:** `/var/www/kontra_day/content` on .13 (mounted read-write into the container).
+- **Article file = `index.md` inside its own folder** (folder name = URL slug, lowercase).
+- Lowercase filenames, no spaces. Images in the **same folder** as the article.
+- `_`-prefixed files are skipped by the site (`_subject.yaml`, `_templates/`, `.trash/`).
+
+## 2. Editing
+
+**Obsidian (daily):** open `/home/sam/obsidian/kontra_content` as a vault; **GitHub Sync** auto-pushes.
+New article: **ββ§T β article** (substitutes `{{title}}`/`{{date}}` β hand-copying leaves them literal & breaks front-matter).
+- `published: false` hides everywhere; omit = published. `featured: true` = hero.
+
+**Decap (browser):** login with Gitea β nested tree `Articles β world β chinese-chipsβ¦`. Same result, commits to Gitea automatically.
+
+## 3. Media
+
+Images are **git files** beside their article (no S3).
+- Body: ``
+- Hero front-matter: `image: "{{media:chinese-chips/hero.jpg}}"` (**must be quoted**)
+- Rule: `{{media:/}}` β lowercase slug, **exact filename incl. extension**, no leading slash, no backticks.
+- App serves `/media/` mapping slugβsubject folder (safe-path guarded).
+
+## 4. Admin (Decap) config
+
+`app/src/web/admin/config.yml` (embedded at build). **Nested collections** list the 2-level tree:
+```yaml
+collections:
+ - name: articles
+ folder: subjects
+ create: true
+ slug: "{{slug}}"
+ nested: { depth: 100, subfolders: false } # subfolders:false REQUIRED
+ meta: { path: { widget: string, label: 'Path', index_file: 'index' } }
+```
+**Gotchas:** `index_file:'index'` not `_index`; `subfolders:false` not true; no apostrophes inside single-quoted labels (use double quotes); media library disabled (`media_folder:""`). Auth = Gitea OAuth (app `decap-kontra`, PKCE).
+
+## 5. Ops
+
+**Machines:** Gitea .35:3001 (origin) Β· Kontra container .13:8600 Β· Caddy master .35:443 Β· dev .27.
+
+**Build** (after source changes, on .27): `cd app && ./build.sh`
+
+**Deploy to .13:**
+```bash
+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`; use `--delete` (stale files like old `media.go` break builds).
+- Content autosync: push β container pulls 30s β restart. No CI/CD.
+- Secrets: Gitea token in `.env` (gitignored, chmod 600).
+
+## 6. Backups
+Content + images in git β backed by Gitea + production clone on .13. App source in git. Offsite per Backup Architecture.
+
+## 7. For AI agents resuming
+1. Read this file first.
+2. Site is live; smallest change + `rsync --delete` deploy + verify on the live URL.
+3. Treat `config.yml` as sacred (nested `subfolders:false` + `index_file:index`).
+4. Media = git files; `{{media:/}}`, quoted in front-matter, exact filename.
+5. Don't reintroduce S3/Garage (dropped 2026-09-10).
---
-
-## What tools were used
-
-| Tool | Role | Where |
-|---|---|---|
-| **Go (golang) 1.26** | App language β one static binary | `app/` |
-| **templ v0.3** | HTML template compile-to-Go | `app/src/*.templ` |
-| **htmx** | Server-rendered interactivity | templates |
-| **goldmark** | CommonMark Markdown renderer | `app/src/content.go` |
-| **goccy/go-yaml** | Front-matter / config parsing | `app/src/content.go` |
-| **Decap CMS** | Browser admin | `app/src/web/admin/` (embedded), served at `/web/admin/` |
-| **Gitea** | Git origin (both repos: `sam/kontra`, `sam/kontra-content`) | .35 Docker :3001 |
-| **Garage S3** | Media objects (`kontra-day` bucket) | .13 Docker :3900 |
-| **Caddy** | TLS + routing, master reverse proxy | .35 Docker :80/:443 |
-| **Docker / compose** | Runtime container `kontra-kontra` | .13 |
-| **Design tokens (CSS vars)** | Theming from `DESIGN.md` | `app/src/web/assets/kontra.css` |
-| **Mermaid + Archify** | Architecture docs + interactive map | `docs/` |
-| **Obsidian Git** | Vault auto-push | .27 |
-
----
-
-## Where things are
-
-| Thing | Location |
-|---|---|
-| App source + deploy + docs | `/home/sam/home_network/web_sites/kontra_day/` (.27) |
-| Public repo (README, app, docs) | `https://gitea.lab.audasmedia.com.au/sam/kontra` |
-| Content repo (origin) | `https://gitea.lab.audasmedia.com.au/sam/kontra-content` |
-| Production content clone | `.13:/var/www/kontra_day/content` |
-| Docker project | `.13:/home/sam/Docker/Containers/kontra/` |
-| Binary (host build) | `app/bin/` after `app/build.sh` on .27 |
-| Obsidian vault | `/home/sam/obsidian/kontra_content` |
-| Interactive map | `https://maps.lab.audasmedia.com.au/kontra_day/docs/` |
-| Plan/decisions | `PLAN.md` (repo) |
-| Design system | `DESIGN.md` (repo) |
-
----
-
-## Configs for the future (handover notes)
-
-### Networking / proxy
-- Master Caddy on .35 (`/Docker/Containers/caddy/Caddyfile`) β `kontra.lab.audasmedia.com.au` and `admin.kontra.lab.audasmedia.com.au` both `reverse_proxy 192.168.20.13:8600`.
-- The .13 Caddy (port 8000) is NOT involved for Kontra β it was reverted back to pre-Kontra config (backup: `/etc/nixos/configuration.nix.bak-kontra-20260908-195305`). **Do not re-add Kontra to configuration.nix** β it now runs in Docker (NixOS service files in `/etc/systemd/system` are read-only Nix-store managed).
-
-### Gitea / OAuth (Decap login)
-- OAuth app `decap-kontra` (client id **ffe1375f-1a1b-40d2-9602-27b0f5b470fe**, non-confidential/PKCE) β in Gitea β Settings β Applications.
-- Gitea CORS enabled for `https://admin.kontra.lab.audasmedia.com.au` (via `GITEA__cors__*` env in `/Docker/Containers/gitea/docker-compose.yml`).
-- Decap config: `app/src/web/admin/config.yml` (`backend: name: gitea`). Simple workflow only (Gitea backend does not support editorial_workflow PRs).
-
-### Environment / secrets
-- Gitea deploy token + API base: `.env` at repo root on .27 (**gitignored β never commit**). File perms 600.
-
-### Deploy specifics
-- Docker image builds Go + templ inside (`golang:1.26-bookworm`), runs on a slim runner. Port 8600.
-- Container mounts content (rw) + `/home/sam/.ssh/id_ed25519` (ro) for the git pull to Gitea.
-- The 30s pull loop uses `exec` so the web-server PID is always the direct child (previous bug: nested subshell caused "address already in use" on restart β fixed).
-
-### Known-good state (verified 2026-09-09)
-- Push new article β live in ~30 s. Push delete β gone in ~30 s. Clean restarts.
-- `https://kontra.lab.audasmedia.com.au/` (front page with Topics grid), `/articles/`, `/subjects/` all 200.
-- Admin renders the Decap login page; "Login with Gitea" β OAuth flow works.
-
----
-
-*Portfolio/showcase link: https://gitea.lab.audasmedia.com.au/sam/kontra (public) β README includes the interactive map, stack, and deploy story. Map: https://maps.lab.audasmedia.com.au/kontra_day/docs/kontra-architecture-map.html*
\ No newline at end of file
+*Full repo copy of this guide: `docs/KONTRA-GUIDE.md`. Architecture map published via Archify β maps.lab.*
\ No newline at end of file