diff --git a/docs/OVERVIEW.md b/docs/OVERVIEW.md new file mode 100644 index 0000000..fd9568c --- /dev/null +++ b/docs/OVERVIEW.md @@ -0,0 +1,153 @@ +# Kontra β€” Project Overview + +> **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. 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** (needs push access to `sam/kontra-content`). +2. **Articles** collection: create/edit, set subject/kicker/excerpt/featured, write Markdown body, Save. Wait ~30 s β€” the site updates. + +Or edit files directly: Obsidian vault (`/home/sam/obsidian/kontra_content`) or clone +`ssh://git@gitea.lab.audasmedia.com.au:2222/sam/kontra-content.git`, edit, push. + +### Content model +``` +content/ +β”œβ”€β”€ config/site.yaml # site name, tagline, nav order +β”œβ”€β”€ subjects// +β”‚ β”œβ”€β”€ _subject.yaml # name, template, accent color +β”‚ └── .md # article +└── pages/.md +``` +Template resolution: page front-matter β†’ subject `_subject.yaml` β†’ site config β†’ fallback. +Media: `{{media:file.jpg}}` (Garage S3), `{{embed:vimeo:ID}}`. + +### As an operator +- Restart/redeploy: `ssh sam@192.168.20.13` β†’ `cd /home/sam/Docker/Containers/kontra && docker compose up -d --build` +- Pull loop: `entrypoint.sh` in the container (git pull every 30s, restart on change). +- Logs: `docker logs -f kontra` +- Content: `/var/www/kontra_day/content` on .13 (rw mount into container). + +## Tools used + +| Tool | Role | Where | +|---|---|---| +| Go (golang) 1.26 | App language β€” one static binary | `app/` | +| templ v0.3 | HTML templates compile-to-Go | `app/src/*.templ` | +| htmx | Server-rendered interactivity | templates | +| goldmark | CommonMark renderer | `app/src/content.go` | +| goccy/go-yaml | Front-matter/config parsing | `app/src/content.go` | +| Decap CMS | Browser admin | `app/src/web/admin/` | +| Gitea | Git origin (both repos) | .35 Docker :3001 | +| Garage S3 | Media (`kontra-day` bucket) | .13 Docker :3900 | +| Caddy | TLS + routing | .35 :80/:443 | +| Docker / compose | Runtime container | .13 | +| Design tokens (CSS vars) | Theming from `DESIGN.md` | `app/src/web/assets/kontra.css` | +| Mermaid + Archify | 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 | `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/` | +| Obsidian vault | `/home/sam/obsidian/kontra_content` | +| Interactive map | `https://maps.lab.audasmedia.com.au/kontra_day/docs/` | +| Plan/decisions | `PLAN.md` | +| Design system | `DESIGN.md` | + +## Configs for the future (handover notes) + +### Networking +- Master Caddy (.35, `/Docker/Containers/caddy/Caddyfile`) β†’ both `kontra.lab…` and `admin.kontra…` β†’ `reverse_proxy 192.168.20.13:8600`. +- .13 Caddy config is back to pre-Kontra. **Kontra runs in Docker β€” do not re-add to configuration.nix** (NixOS system units are Nix-store-managed). + +### Gitea / OAuth +- OAuth app `decap-kontra` (non-confidential / PKCE) in Gitea β†’ Settings β†’ Applications. +- Gitea CORS enabled for the admin origin (`GITEA__cors__*` in `/Docker/Containers/gitea/docker-compose.yml`). +- Decap `config.yml`: `backend: name: gitea`, simple workflow (no editorial workflow PRs β€” Gitea backend limitation). + +### Secrets +- Gitea deploy token + API base: `.env` at repo root (.27), **gitignored, never commit**. chmod 600. + +### Deploy specifics +- Docker image builds Go+templ in-image; runner is slim. Port 8600. +- Container mounts content (rw) + `/home/sam/.ssh/id_ed25519` (ro) for the pull. +- Pull loop uses `exec` so the server PID is always direct (prior nested-subshell bug caused "address already in use" on restart β€” fixed). + +### Known-good state (verified 2026-09-09) +- Add/delete content β†’ live in ~30 s. Clean restarts. +- Front page, `/articles/`, `/subjects/` all 200. +- Admin renders Decap login; "Login with Gitea" OAuth flow works. + +--- + +*Portfolio/showcase: 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