Files
obsidian-vault/200 projects/240 Web Sites/Kontra Day.md

5.1 KiB

created, modified, type, status, tags, aliases
created modified type status tags aliases
2026-09-09 11:54 2026-09-10 16:30 project active
politics
portfolio
kontra
goth
golang
self-hosted
editing
admin
ops
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

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)"]
    G[(sam/kontra-content<br/>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:<slug>/<exact-file>}} — lowercase slug, exact filename incl. extension, no leading slash, no backticks.
  • App serves /media/<path> 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:

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:

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:<slug>/<file>}}, 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.