From c8a02d403fb772166f3dcb4453c549b990e291b0 Mon Sep 17 00:00:00 2001 From: Sam Rolfe Date: Wed, 9 Sep 2026 12:24:08 +1000 Subject: [PATCH] sam-4screen-desktop 2026-9-9:12:24:8 --- 200 projects/240 Web Sites/Kontra Day.md | 175 ++++++++++++++++++++++- 1 file changed, 172 insertions(+), 3 deletions(-) diff --git a/200 projects/240 Web Sites/Kontra Day.md b/200 projects/240 Web Sites/Kontra Day.md index 9ebd52d..95cb852 100644 --- a/200 projects/240 Web Sites/Kontra Day.md +++ b/200 projects/240 Web Sites/Kontra Day.md @@ -1,13 +1,182 @@ --- created: 2026-09-09 11:54 -modified: 2026-09-09 11:54 +modified: 2026-09-09 12:05 type: note tags: - website - politics - portfolio -aliases: [] + - kontra + - goth + - golang + - self-hosted +aliases: + - Kontra + - Kontra Day --- -# [[Kontra Day]] +# Kontra +> **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. + +**Live:** https://kontra.lab.audasmedia.com.au ยท **Admin:** https://admin.kontra.lab.audasmedia.com.au/web/admin/ +**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"] + 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 (192.168.20.35 :3001 Docker)"] + G[(sam/kontra-content
markdown origin of truth)] + 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)] + end + + subgraph media["Media (Garage S3 on .13)"] + S3[(Garage S3
kontra-day bucket)] + 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 +``` + +**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 + +### 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 +``` +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). + +--- + +## 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