--- created: 2026-09-09 11:54 modified: 2026-09-10 16:30 type: note tags: - website - politics - portfolio - kontra - goth - golang - self-hosted - editing - media - admin - ops aliases: - Kontra - Kontra Day --- # Kontra — Editing, Media, Admin & Ops > **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 (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 ```mermaid flowchart LR subgraph editors["Content Editors"] O[Obsidian vault
/obsidian/kontra_content] -->|git push| G N[Neovim / CLI] -->|git push| G D[Decap CMS
admin.kontra.lab] -->|OAuth + git commit| G end subgraph gitea["Gitea (.35)"] G[(sam/kontra-content
md + images = origin)] end subgraph prod["Kontra container (.13 :8600)"] P[git pull 30s] -->|restart on change| B B[Go: templ+htmx+Goldmark] end subgraph media["Media (git files)"] F[(images beside articles)] end G -->|SSH| P B -->|"{{media:slug/file}}"| F B -->|HTML| CB[Caddy .35 :443] --> WWW[browsers] ``` --- ## 1. Content structure (rules) ``` content/ ├── 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 ``` - **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: `![Alt]({{media:chinese-chips/hero.jpg}})` - 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). --- *Full repo copy of this guide: `docs/KONTRA-GUIDE.md`. Architecture map published via Archify → maps.lab.*