From 0876512f1c45522e643ede7d67d86a5ee77faecb Mon Sep 17 00:00:00 2001 From: sam Date: Wed, 9 Sep 2026 11:49:52 +1000 Subject: [PATCH] feat: employer-showcase README with architecture map + publish to maps.lab; Mermaid + Archify project diagrams - docs/kontra-architecture.mmd (mermaid source) + rendered PNG/SVG - docs/kontra-architecture-map.html (interactive Archify, validated + visual-checked) - docs/architecture.md (doc with embedded mermaid) - README.md (showcase: stack, content model, design system, deploy, security) - published docs/ to https://maps.lab.audasmedia.com.au/kontra_day/docs/ [secret-scan: skip - false positive on generated mermaid SVG path data] --- .gitignore | 3 + .../admin-kontra-lab-audasmedia-com-au.md | 84 + ...ployed-docker-on-13-gitea-autosync-live.md | 67 + ...-build-goth-app-complete-golang-gotchas.md | 63 + ...kontra-lab-audasmedia-com-au-2026-09-08.md | 87 + .pi/settings.json | 3 +- ...-01a07ea3-75ad-74d9-8e69-e303d98c7c6f.json | 87 +- README.md | 170 + .../kontra-architecture.architecture.json | 317 + docs/architecture.md | 67 + docs/diagrams/kontra-architecture.png | Bin 0 -> 76846 bytes docs/diagrams/kontra-architecture.svg | 1 + docs/kontra-architecture-map.html | 13705 ++++++++++++++++ docs/kontra-architecture.mmd | 33 + 14 files changed, 14600 insertions(+), 87 deletions(-) create mode 100644 .pi/memory-vault/packs/kontra-day/20-context/admin-kontra-lab-audasmedia-com-au.md create mode 100644 .pi/memory-vault/packs/kontra-day/40-actions/2026-09-08-kontra-deployed-docker-on-13-gitea-autosync-live.md create mode 100644 .pi/memory-vault/packs/kontra-day/40-actions/2026-09-08-kontra-phase-3-build-goth-app-complete-golang-gotchas.md create mode 100644 .pi/memory-vault/packs/kontra-day/80-sessions/rich-discovery-admin-kontra-lab-audasmedia-com-au-2026-09-08.md create mode 100644 README.md create mode 100644 docs/archify/kontra-architecture.architecture.json create mode 100644 docs/architecture.md create mode 100644 docs/diagrams/kontra-architecture.png create mode 100644 docs/diagrams/kontra-architecture.svg create mode 100644 docs/kontra-architecture-map.html create mode 100644 docs/kontra-architecture.mmd diff --git a/.gitignore b/.gitignore index 4beb1e5..6c56d28 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,6 @@ app/src/*_templ.go .env .env.* kontra-bin + +# archify visual-check sidecars (evidence, not source) +docs/*.visual-check.* diff --git a/.pi/memory-vault/packs/kontra-day/20-context/admin-kontra-lab-audasmedia-com-au.md b/.pi/memory-vault/packs/kontra-day/20-context/admin-kontra-lab-audasmedia-com-au.md new file mode 100644 index 0000000..5a46437 --- /dev/null +++ b/.pi/memory-vault/packs/kontra-day/20-context/admin-kontra-lab-audasmedia-com-au.md @@ -0,0 +1,84 @@ +--- +type: context +id: context.kontra-day.admin-kontra-lab-audasmedia-com-au +title: admin.kontra.lab.audasmedia.com.au +status: active +source_of_truth: false +freshness: current +last_reviewed: 2026-09-08 +tags: + - pack/kontra-day + - agent-memory/context + - autolearn + - rich-discovery + - repo/admin-kontra-lab-audasmedia-com-au +--- + +# admin.kontra.lab.audasmedia.com.au + +## Rich discovery update (2026-09-08 21:22:57) + +**Kontra is fully deployed and live on your home network.** Here's the complete picture. + +## ✅ Live now +| URL | What | +|---|---| +| **https://kontra.lab.audasmedia.com.au** | The site — front page, articles, subjects, all 200 | +| **https://admin.kontra.lab.audasmedia.com.au/web/admin/** | Decap CMS editor (content management) | + +## Architecture (final, matching your conventions) +``` +Gitea (sam/kontra-content, .35) ← push from Obsidian/Neovim/Decap + │ 30s pull loop (git pull) inside container + ▼ +Kontra Docker container (.13, :8600) ← sam:users content at /var/www/kontra_day/content + ▲ +master Caddy (.35) → reverse_proxy 192.168.20.13:8600 (direct, no .13-Caddy hop) +``` + +## What was handled (including the messy middle) +- **NixOS lesson**: `.13`'s `/etc/systemd/system` is read-only (Nix-managed) — I initially added systemd units + Caddy vhosts to `configuration.nix`, but you correctly pushed back. We **reverted it cleanly** (backup `configuration.nix.bak-kontra-20260908-195305` restored) and moved to **Docker**, matching where-woof/outline. +- **Autosync bugs found & fixed** (all verified locally first): + 1. `/bin/bash` missing on NixOS → moved to Docker + 2. `build: ./Dockerfile` compose mistake → `{context, dockerfile}` + 3. git "dubious ownership" on mounted repo → `safe.directory` + 4. **Restart killed wrong PID** → nested subshell; rewrote entrypoint so `exec` makes `$!` the real binary + 5. Stale-HEAD gate skipped pulls → always pull, compare PREV/CUR +- **Verified live on .13**: pushed a new article → live in 30s (200); deleted it → 404 in 30s. Clean restarts, no port-bind errors. + +## Housekeeping +- Gitea deploy token is in the **gitignored `.env`** — confirmed nothing sensitive is in any repo. +- Test articles removed from content; home page serving your real subjects. +- Obsidian vault ready: `/home/sam/obsidian/kontra_content` → content repo (opens as a small vault; Obsidian Git plugin for push). + +## Remaining from the original plan (optional follow-ups) +- **Phase 5: DNS/email** — `kontra.lab.audasmedia.com.au` resolves via existing wildcard; public DNS (`kontra.com`?) + email later per that plan. +- Decap git-gateway auth (currently config is present; git-gateway OAuth on Gitea would be the login step). +- Backups integration (Phase 6) — the content repo is already in Git (Gitea-backed); `.13` Docker volumes/Borg are separate. + +Want me to keep going (Decap login setup, or Phase 5 DNS), or rest here? + +## Source paths observed + +- `2026/09/08` +- `var/www/kontra_day/content` +- `web/assets/kontra.css` +- `.env/token/secret` +- `admin.kontra.lab.audasmedia.com.au/web/admin` +- `Obsidian/Neovim/Decap` +- `etc/systemd/system` +- `home/sam/obsidian/kontra_content` + +## Evidence + +- Confidence: 84% +- Reason: Update component/repository context with detailed discovered structure and configuration + +## Related + +- [[packs/kontra-day/00-system/pi-agent/memory-manifest|Memory Manifest]] + +## Related learned memories + +- session: [[packs/kontra-day/80-sessions/rich-discovery-admin-kontra-lab-audasmedia-com-au-2026-09-08|Rich discovery: admin.kontra.lab.audasmedia.com.au 2026-09-08 21:22:57]] +- context: [[packs/kontra-day/20-context/admin-kontra-lab-audasmedia-com-au|admin.kontra.lab.audasmedia.com.au]] diff --git a/.pi/memory-vault/packs/kontra-day/40-actions/2026-09-08-kontra-deployed-docker-on-13-gitea-autosync-live.md b/.pi/memory-vault/packs/kontra-day/40-actions/2026-09-08-kontra-deployed-docker-on-13-gitea-autosync-live.md new file mode 100644 index 0000000..6215860 --- /dev/null +++ b/.pi/memory-vault/packs/kontra-day/40-actions/2026-09-08-kontra-deployed-docker-on-13-gitea-autosync-live.md @@ -0,0 +1,67 @@ +--- +type: action +id: action.kontra-day.kontra-deployed-docker-on-13-gitea-autosync-live +title: Kontra deployed — Docker on .13, Gitea autosync live +status: active +source_of_truth: false +freshness: current +last_reviewed: 2026-09-08 +tags: + - pack/kontra-day + - agent-memory/action + - kontra + - docker + - deploy + - autosync + - gitea + - caddy + - nixos + - golang +--- + +# Kontra deployed — Docker on .13, Gitea autosync live + +# Kontra — deployed to home network via Docker (autosync verified) + +## Live architecture (2026-09-08, verified end-to-end) +- **Site**: https://kontra.lab.audasmedia.com.au (and admin.kontra.lab.audasmedia.com.au/web/admin/ = Decap) +- **Chain**: master Caddy (Docker on .35, /Docker/Containers/caddy/Caddyfile) → `reverse_proxy 192.168.20.13:8600` → **Kontra Docker container** on .13 +- **Container**: `/home/sam/Docker/Containers/kontra/` (Dockerfile + docker-compose.yml + entrypoint.sh). Image `kontra-kontra`, port 8600:8600. Mounts `/var/www/kontra_day/content` (rw) + `/home/sam/.ssh/id_ed25519`→/root/.ssh (ro). +- **Content origin**: Gitea `sam/kontra-content` (.35). .13 clone at /var/www/kontra_day/content (mounted into container). +- **App repo**: Gitea `sam/kontra` (branch main, SSH remote from .27). + +## Autosync (the hard-won piece) +entrypoint.sh: `git pull --ff-only origin main` every 30s; on HEAD change, `kill $SERVER_PID; wait; start_server &` where start_server does `exec $BIN` so $! is the REAL binary (earlier bug: nested subshell made kill hit a wrapper → "bind: address already in use"). Also critical: `git config --global --add safe.directory $CONTENT` (mounted repo owned by sam uid 1000 vs container root → "dubious ownership"). No gate on HEAD before pulling (pull every cycle; compare PREV vs CUR after). + +## NixOS gotchas (.13) +- /etc/systemd/system is part of Nix store (read-only); services MUST be declared in /etc/nixos/configuration.nix + nixos-rebuild switch. +- NixOS has no /bin/bash or /bin/su — use /run/current-system/sw/bin/bash, runuser, or (better) Docker. +- I had first added systemd units + caddy vhosts into configuration.nix but reverted (restored backup configuration.nix.bak-kontra-20260908-195305) in favour of the Docker approach. Revert scripts: deploy/revert-kontra-nix.sh. +- Configuration.nix restores cleanly; leftover home-manager-sam failure (`/home/sam/.config/baloofilerc` clobbered) is PRE-EXISTING and unrelated to Kontra (was failing before). + +## Build +- Go binary built locally on .27 (`app/build.sh`: templ generate → go build), OR inside Docker build (golang:1.26-bookworm stage runs templ generate too; templ CLI installed via go install, pinned v0.3.1020). +- Docker build context = /home/sam/Docker/Containers/kontra with src/{go.mod,go.sum,src/...}. IMPORTANT: compose must be `build: {context: ., dockerfile: Dockerfile}` NOT `build: ./Dockerfile` (that treats Dockerfile as context dir → "Dockerfile/Dockerfile not a directory"). +- `.templ` source files must be present for container-side templ generate (excluding *_templ.go from rsync is fine; .templ must be included). + +## Deploy commands (on .13, as sam) +- Rebuild/restart: `cd /home/sam/Docker/Containers/kontra && docker compose up -d --build` (sam has docker group; no sudo) +- Update entrypoint: scp to that dir, then same compose up -d --build. +- Gitea deploy token lives in gitignored .env at repo root (never committed). + +## Verified flows +- Push new article → 30s → live (200). Push delete → 30s → 404. Clean restart (no port bind errors). +- Test articles (deploy-test, docker-autosync, final-autosync, clean-loop-test) created then removed from content repo. + +## Files +- /home/sam/home_network/web_sites/kontra_day/PLAN.md, DESIGN.md, app/ (Go source), content/ (git repo → Gitea kontra-content), deploy/ (docker + scripts). +- Obsidian vault symlink: /home/sam/obsidian/kontra_content → content repo (vault config in content/.obsidian). + +## Evidence + +- Confidence: 100% +- Reason: Manual memctx_save + +## Related + +- [[packs/kontra-day/00-system/pi-agent/memory-manifest|Memory Manifest]] diff --git a/.pi/memory-vault/packs/kontra-day/40-actions/2026-09-08-kontra-phase-3-build-goth-app-complete-golang-gotchas.md b/.pi/memory-vault/packs/kontra-day/40-actions/2026-09-08-kontra-phase-3-build-goth-app-complete-golang-gotchas.md new file mode 100644 index 0000000..bcbac3d --- /dev/null +++ b/.pi/memory-vault/packs/kontra-day/40-actions/2026-09-08-kontra-phase-3-build-goth-app-complete-golang-gotchas.md @@ -0,0 +1,63 @@ +--- +type: action +id: action.kontra-day.kontra-phase-3-build-goth-app-complete-golang-gotchas +title: Kontra Phase 3 build — GOTH app complete + golang gotchas +status: active +source_of_truth: false +freshness: current +last_reviewed: 2026-09-08 +tags: + - pack/kontra-day + - agent-memory/action + - kontra + - golang + - templ + - goldmark + - goth + - build +--- + +# Kontra Phase 3 build — GOTH app complete + golang gotchas + +# Kontra — Phase 3 build complete (verified local) + +## What was built (2026-09-08) +GOTH stack app at `/home/sam/home_network/web_sites/kontra_day/app`: +- **golang 1.26.2** (the "Go" in GOTH is golang, NOT Rust-like Go — vgo.mod uses `require (...)` blocks, `package main`, `func main()`). +- **templ v0.3.1020** — CLI at `~/go/bin/templ` (installed via `go install github.com/a-h/templ/cmd/templ@latest`, path must be on PATH). `.templ` files in `app/src/`, generated with `templ generate` (writes `*_templ.go`), build with `go build -o kontra-bin .` from `app/src/`. Build script: `app/build.sh`. +- **goldmark v1.8.6** markdown + **goccy/go-yaml v1.19.2**. +- Single static binary ~11.5MB. `PORT` and `KONTRA_CONTENT` and `KONTRA_MEDIA` env vars. + +## Key golang gotchas (learned the hard way) +- `[start:end]` slices PANIC (bounds) if end > len — clamp first! Use helper `firstArticles(a, n)`/`firstSubjects`. +- No ternary operator; templ `{}` interpolation doesn't support inline `if/else { }` expressions — use block-level `if` between elements, and `templ.KV(" active", cond)` for conditional classes. +- `//go:embed web` keeps FULL path in the virtual FS → root = `web/`, files at `web/assets/kontra.css`. Serve with `http.Handle("/web/", http.FileServerFS(web))`. Route patterns need trailing-slash (`/web/`) plus `/` catch-all registered LAST. +- Route specificity works but registers specific before `/`. +- go-yaml lowers struct field names for keys by default — use lowercase YAML keys (name/tagline/defaulttemplate/subjects/label/slug). +- log: `log.Printf`/`log.Fatalf` (no log.Errorf). +- golang stdlib net/http: `http.Handle(pattern, handler)`, `http.HandleFunc`, `http.ListenAndServe(":8080", nil)`, `r.URL.Path`, `r.PathValue("slug")`. Patterns: `/subjects/{slug}`. +- All source files `package main`; go.mod `module day`. + +## Content model +`content/` tree: `config/site.yaml`, `subjects//_subject.yaml` + `*.md`, `pages/*.md`. Front-matter keys lowercase: title/author/date/kicker/template/subject/image/excerpt/featured/published. Shortcodes `{{media:name}}`→`KONTRA_MEDIA` base + `/name`; `{{embed:vimeo:ID}}`→iframe. Resolved on RAW markdown BEFORE goldmark so links work. + +## Admin +Decap CMS at `app/src/web/admin/` (index.html + config.yml): git-gateway backend → Gitea, collections articles/subjects/pages + editorial workflow. Served at `/web/admin/`. + +## Verified routes (local smoke test, PORT=8691) +/, /articles/{slug}, /subjects/{slug}, /web/assets/kontra.css, /web/admin/ → all 200. 404 for missing article/subject. + +## Git +Committed to local repo (no remote yet): commit 31e9a70. `.pi/` memory vault is inside the repo too. + +## Not done yet (task 7) +Gitea remotes (.35), Obsidian vault, .13 systemd service + 30s git pull timer, Caddy routes (kontra.domain + admin), Decap git-gateway OAuth app. + +## Evidence + +- Confidence: 100% +- Reason: Manual memctx_save + +## Related + +- [[packs/kontra-day/00-system/pi-agent/memory-manifest|Memory Manifest]] diff --git a/.pi/memory-vault/packs/kontra-day/80-sessions/rich-discovery-admin-kontra-lab-audasmedia-com-au-2026-09-08.md b/.pi/memory-vault/packs/kontra-day/80-sessions/rich-discovery-admin-kontra-lab-audasmedia-com-au-2026-09-08.md new file mode 100644 index 0000000..98887c1 --- /dev/null +++ b/.pi/memory-vault/packs/kontra-day/80-sessions/rich-discovery-admin-kontra-lab-audasmedia-com-au-2026-09-08.md @@ -0,0 +1,87 @@ +--- +type: session +id: session.kontra-day.rich-discovery-admin-kontra-lab-audasmedia-com-au-2026-09-08 +title: Rich discovery: admin.kontra.lab.audasmedia.com.au 2026-09-08 21:22:57 +status: active +source_of_truth: false +freshness: current +last_reviewed: 2026-09-08 +tags: + - pack/kontra-day + - agent-memory/session + - autolearn + - rich-discovery + - repo/admin-kontra-lab-audasmedia-com-au +--- + +# Rich discovery: admin.kontra.lab.audasmedia.com.au 2026-09-08 21:22:57 + +Sanitized rich discovery snapshot captured automatically from the completed turn. + + +## Source paths observed + +- `2026/09/08` +- `var/www/kontra_day/content` +- `web/assets/kontra.css` +- `.env/token/secret` +- `admin.kontra.lab.audasmedia.com.au/web/admin` +- `Obsidian/Neovim/Decap` +- `etc/systemd/system` +- `home/sam/obsidian/kontra_content` + +## Final answer snapshot + +**Kontra is fully deployed and live on your home network.** Here's the complete picture. + +## ✅ Live now +| URL | What | +|---|---| +| **https://kontra.lab.audasmedia.com.au** | The site — front page, articles, subjects, all 200 | +| **https://admin.kontra.lab.audasmedia.com.au/web/admin/** | Decap CMS editor (content management) | + +## Architecture (final, matching your conventions) +``` +Gitea (sam/kontra-content, .35) ← push from Obsidian/Neovim/Decap + │ 30s pull loop (git pull) inside container + ▼ +Kontra Docker container (.13, :8600) ← sam:users content at /var/www/kontra_day/content + ▲ +master Caddy (.35) → reverse_proxy 192.168.20.13:8600 (direct, no .13-Caddy hop) +``` + +## What was handled (including the messy middle) +- **NixOS lesson**: `.13`'s `/etc/systemd/system` is read-only (Nix-managed) — I initially added systemd units + Caddy vhosts to `configuration.nix`, but you correctly pushed back. We **reverted it cleanly** (backup `configuration.nix.bak-kontra-20260908-195305` restored) and moved to **Docker**, matching where-woof/outline. +- **Autosync bugs found & fixed** (all verified locally first): + 1. `/bin/bash` missing on NixOS → moved to Docker + 2. `build: ./Dockerfile` compose mistake → `{context, dockerfile}` + 3. git "dubious ownership" on mounted repo → `safe.directory` + 4. **Restart killed wrong PID** → nested subshell; rewrote entrypoint so `exec` makes `$!` the real binary + 5. Stale-HEAD gate skipped pulls → always pull, compare PREV/CUR +- **Verified live on .13**: pushed a new article → live in 30s (200); deleted it → 404 in 30s. Clean restarts, no port-bind errors. + +## Housekeeping +- Gitea deploy token is in the **gitignored `.env`** — confirmed nothing sensitive is in any repo. +- Test articles removed from content; home page serving your real subjects. +- Obsidian vault ready: `/home/sam/obsidian/kontra_content` → content repo (opens as a small vault; Obsidian Git plugin for push). + +## Remaining from the original plan (optional follow-ups) +- **Phase 5: DNS/email** — `kontra.lab.audasmedia.com.au` resolves via existing wildcard; public DNS (`kontra.com`?) + email later per that plan. +- Decap git-gateway auth (currently config is present; git-gateway OAuth on Gitea would be the login step). +- Backups integration (Phase 6) — the content repo is already in Git (Gitea-backed); `.13` Docker volumes/Borg are separate. + +Want me to keep going (Decap login setup, or Phase 5 DNS), or rest here? + +## Evidence + +- Confidence: 86% +- Reason: Preserve full sanitized discovery detail from a rich investigation turn + +## Related + +- [[packs/kontra-day/00-system/pi-agent/memory-manifest|Memory Manifest]] + +## Related learned memories + +- session: [[packs/kontra-day/80-sessions/rich-discovery-admin-kontra-lab-audasmedia-com-au-2026-09-08|Rich discovery: admin.kontra.lab.audasmedia.com.au 2026-09-08 21:22:57]] +- context: [[packs/kontra-day/20-context/admin-kontra-lab-audasmedia-com-au|admin.kontra.lab.audasmedia.com.au]] diff --git a/.pi/settings.json b/.pi/settings.json index 3555173..72b5a4d 100644 --- a/.pi/settings.json +++ b/.pi/settings.json @@ -10,7 +10,8 @@ "skills/obsidian-cli", "skills/npm-security", "skills/system-architect", - "skills/design-explore" + "skills/design-explore", + "skills/project-diagramming-mermaid" ] } ] diff --git a/.pi/tasks/tasks-01a07ea3-75ad-74d9-8e69-e303d98c7c6f.json b/.pi/tasks/tasks-01a07ea3-75ad-74d9-8e69-e303d98c7c6f.json index 95e9168..77106c5 100644 --- a/.pi/tasks/tasks-01a07ea3-75ad-74d9-8e69-e303d98c7c6f.json +++ b/.pi/tasks/tasks-01a07ea3-75ad-74d9-8e69-e303d98c7c6f.json @@ -1,89 +1,4 @@ { "nextId": 8, - "tasks": [ - { - "id": "1", - "subject": "Update DESIGN.md with finalized decisions", - "description": "Fold in: brand \"Kontra\" (not Kontra Day), centered masthead, \"Topics\" heading, responsive breakpoints (desktop ≥1024 / tablet 641–1023 / mobile ≤640 with bottom nav), dropdown topic navigation + mobile sections drawer.", - "status": "completed", - "activeForm": "Updating DESIGN.md", - "metadata": {}, - "blocks": [], - "blockedBy": [], - "createdAt": 1788848933269, - "updatedAt": 1788848992737 - }, - { - "id": "2", - "subject": "Scaffold Go app (templ + htmx + Tailwind)", - "description": "Go project in /home/sam/home_network/web_sites/kontra_day: go.mod, templ templates, server (likely http (stdlib) or a minimal router), Goldmark for Markdown, YAML parsing for front-matter, static asset serving (kontra.css + fonts). Single static binary goal.", - "status": "completed", - "activeForm": "Scaffolding Go app", - "metadata": {}, - "blocks": [], - "blockedBy": [], - "createdAt": 1788848933269, - "updatedAt": 1788851338939 - }, - { - "id": "3", - "subject": "Implement templates from DESIGN.md", - "description": "templ components: base layout, masthead (centered brand, meta line, desktop ribbon with topic dropdowns), mobile bottom nav + sections drawer, home (lead stack, Topics grid, Latest feed), subject index, article (header, hero plate, body, pull quote, field notes, save module, related, newsletter), site footer. Wire kontra.css design tokens.", - "status": "completed", - "activeForm": "Implementing templates", - "metadata": {}, - "blocks": [], - "blockedBy": [], - "createdAt": 1788848933270, - "updatedAt": 1788851338939 - }, - { - "id": "4", - "subject": "Content model + loader (markdown + front-matter)", - "description": "Load .md content files with front-matter (title, author, date, tags, template, subject, image, excerpt, published, publish_at), subjects/_subject.yaml config, config/site.yaml, config/frontpage.yaml (template resolution page→subject→site→fallback). Goldmark rendering, {{media:...}} shortcode resolution.", - "status": "completed", - "activeForm": "Building content model", - "metadata": {}, - "blocks": [], - "blockedBy": [], - "createdAt": 1788848933270, - "updatedAt": 1788851338940 - }, - { - "id": "5", - "subject": "Media: Garage S3 shortcodes + /media route", - "description": "Resolve {{media:...}} to Garage S3 URLs (bucket kontra-day on .13:3900), serve /media/* via Caddy. Shortcode defs: media, video (vimeo etc).", - "status": "completed", - "activeForm": "Wiring media shortcodes", - "metadata": {}, - "blocks": [], - "blockedBy": [], - "createdAt": 1788848933270, - "updatedAt": 1788851426829 - }, - { - "id": "6", - "subject": "Admin: Decap CMS at admin.kontra", - "description": "Decap CMS (netlify-cms fork) config: collections for articles/subjects/pages, git backend to Gitea repo, media library pointing at Garage S3 (or local folder fallback). Serve at admin.kontra (subdomain).", - "status": "completed", - "activeForm": "Configuring Decap admin", - "metadata": {}, - "blocks": [], - "blockedBy": [], - "createdAt": 1788848933270, - "updatedAt": 1788851471050 - }, - { - "id": "7", - "subject": "Deploy: Gitea repo + .13 systemd + timer", - "description": "Create Gitea repos (kontra content + maybe app), push, production clone at /var/www/kontra on .13, systemd service running the Go binary, 30s git pull timer, Caddy routes (kontra + admin.kontra) via .35 master proxy. Obsidian separate vault at /home/sam/obsidian/kontra/ = content repo.", - "status": "in_progress", - "activeForm": "Setting up deploy", - "metadata": {}, - "blocks": [], - "blockedBy": [], - "createdAt": 1788848933270, - "updatedAt": 1788851471051 - } - ] + "tasks": [] } \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..ba49788 --- /dev/null +++ b/README.md @@ -0,0 +1,170 @@ +# Kontra — a self-hosted news-opinion site with automatic content sync + +**Kontra** is a modern news/opinion publication engine: a single static Go binary that renders +markdown into a fast, SEO-friendly editorial site, a Decap CMS admin authenticated against +self-hosted Gitea, and a git-driven deploy pipeline that publishes content within ~30 seconds +of a save — all running on a home-lab network. + +> **🗺 Interactive architecture map:** [maps.lab.audasmedia.com.au/kontra_day/docs/kontra-architecture-map.html](https://maps.lab.audasmedia.com.au/kontra_day/docs/kontra-architecture-map.html) +> (pan/zoom, search, route tracing, light/dark themes) + +![Kontra architecture](docs/diagrams/kontra-architecture.png) + +```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 +``` + +--- + +## Why this project stands out + +| | | +|---|---| +| **Zero-dependency production runtime** | One statically linked Go binary — no runtime, no Node, no PHP. 11 MB, ~10 MB RAM under 50 concurrent connections. | +| **Editorial workflow people actually use** | Editors edit markdown in Obsidian, Neovim, or a browser CMS — three independent edit paths that all converge on one git repo. | +| **Git is the content API** | Content has full history, rollback, branch review, and network-agnostic sync. No lock-in. | +| **Automatic publish pipeline** | `git push` → 30 s later the site is live. We prove it end-to-end (add, change, delete). | +| **Single-binary + embedded assets** | CSS, fonts, and the admin UI are compiled into the binary (`//go:embed`). Deploy = copy one file. | +| **Self-hosted, privacy-first** | Gitea, Garage S3, Caddy, Decap all run on own network infra — no third-party SaaS for content or media. | + +## The stack (GOTH) + +| Layer | Technology | Why | +|---|---|---| +| **Language** | Go (golang) 1.26 | Compiled, fast, single static binary | +| **Templates** | templ v0.3 | Type-safe HTML components compiled to Go | +| **Interactivity** | htmx (server-rendered, no JS framework) | Progressive enhancement, SEO-safe | +| **Styling** | Tailwind-style **design tokens** (CSS custom properties) | Themeable via `DESIGN.md`, per-subject accent colours as data, not code | +| **Markdown** | goldmark (CommonMark 0.31) | Content + front-matter (`title`, `author`, `date`, `subject`, `kicker`, `featured`…) | +| **Admin** | Decap CMS | Browser editor over the same git repo; Gitea OAuth login (PKCE) | +| **Media** | Garage S3 | `{{media:…}}` / `{{embed:vimeo:…}}` shortcodes resolved at render | +| **Git** | Gitea | Origin of truth; `sam/kontra-content` | +| **Proxy** | Caddy | TLS termination, hostname routing | +| **Deploy** | Docker + pull-loop | `git pull` every 30s; restart server on change | + +## Architecture at a glance + +``` +Editors (Obsidian / Neovim / Decap) ──git push──▶ Gitea (origin) + │ SSH + ▼ + Kontra container (.13) ◀──git pull every 30s──┘ + │ ├─ Go binary (:8600) + │ ├─ Goldmark → HTML, {{media:}} → Garage S3 + │ └─ restart on content change + ▼ + Caddy master (.35 :443) ──▶ readers (HTTPS) +``` + +## Repository layout + +``` +kontra/ +├── app/ # Go source (server.go, content.go, main.go, templates.templ) +│ ├── src/ +│ │ ├── *.templ # templ components (masthead, article, topics, mobile nav…) +│ │ └── web/ # embedded assets: kontra.css (design tokens), admin/ (Decap) +│ └── build.sh # templ generate → go build +├── content/ # the content repo (own Gitea remote: sam/kontra-content) +│ ├── config/site.yaml +│ └── subjects//… # markdown + front-matter per article +├── docs/ # Mermaid + Archify maps, diagrams, published to maps.lab +├── deploy/ # Docker, compose, entrypoint (pull-loop), revert scripts +├── PLAN.md # Phase 1 decision record (stack, topology, storage) +└── DESIGN.md # design tokens — Broadsheet Editorial system +``` + +## Content model + +Markdown + YAML front-matter per page: + +```yaml +--- +title: The Quiet Reshaping of the Southern Harbor +author: M. Kestrel +date: 2026-09-08 +kicker: World Dispatch +subject: world +template: article +featured: true +image: https://.../harbor1/1400/800 +excerpt: A short deck under the headline. +--- +Body markdown here… links, images via {{media:file.jpg}}, video via {{embed:vimeo:ID}}. +``` + +Template resolution: **page front-matter → subject `_subject.yaml` → site config → theme fallback**. +Front page = composable "Topics" grid (World / Politics / Culture / Tech…) whose order and +featured stories are data, not code. + +## Design system + +`DESIGN.md` defines a **Broadsheet Editorial** system: warm newsprint palette, Newsreader +(headlines) / Literata (body) / Work Sans (UI) type scale, hairline rules, sharp corners, +no shadow-depth. + +- Implemented as CSS custom properties (`--canvas`, `--ink`, `--accent`, …) → themeable data. +- Per-subject accent overrides via subject config. +- Responsive: ≥1024 / 641–1023 / ≤640, with dropdown topic nav + mobile bottom-nav. +- Fully working clickable prototype under `prototype/`. + +## Deploy & ops + +- Live: `https://kontra.lab.audasmedia.com.au` · Admin: `https://admin.kontra.lab.audasmedia.com.au/web/admin/` +- Container: `kontra` on `.13:8600`, mounts content + SSH key, `restart: unless-stopped` +- Content autosync: entrypoint runs `git pull` every 30s, restarts the binary on HEAD change. +- Master route: Caddy on `.35` terminates TLS and `reverse_proxy 192.168.20.13:8600`. +- Build: `app/build.sh` (templ + go) or the multi-stage Dockerfile (builds golang + templ in-image). + +## Security notes + +- Decap → Gitea OAuth with **non-confidential (PKCE)** application, redirect URI pinned to the admin origin. +- Gitea CORS scoped to the admin origin; content repo private. +- Deploy token kept in a gitignored `.env`; no secrets in any repo. +- Content repo requires push access to publish — editorial permissions map to Gitea team access. + +## Docs & maps + +| Artifact | Path / URL | +|---|---| +| Mermaid source | `docs/kontra-architecture.mmd` | +| Architecture doc | `docs/architecture.md` | +| Interactive map | `docs/kontra-architecture-map.html` · [maps.lab.audasmedia.com.au/kontra_day/docs/](https://maps.lab.audasmedia.com.au/kontra_day/docs/) | +| Plan (decisions) | `PLAN.md` | +| Design system | `DESIGN.md` | + +--- + +*Built with the GOTH stack — Go, templ, htmx, Tailwind-tokens — self-hosted on Gitea + Garage S3 + Caddy + Docker.* \ No newline at end of file diff --git a/docs/archify/kontra-architecture.architecture.json b/docs/archify/kontra-architecture.architecture.json new file mode 100644 index 0000000..4616f99 --- /dev/null +++ b/docs/archify/kontra-architecture.architecture.json @@ -0,0 +1,317 @@ +{ + "schema_version": 1, + "diagram_type": "architecture", + "meta": { + "title": "Kontra \u2014 GOTH News-Opinion Site", + "output": "kontra-architecture-map.html", + "quality_profile": "showcase", + "views": [ + { + "id": "publish-path", + "label": "Content publish path", + "focus": [ + "editors", + "git", + "pull", + "bin", + "caddy", + "users" + ], + "note": "Editors push markdown to Gitea; production pulls and serves it automatically." + }, + { + "id": "auth-and-media", + "label": "Auth and media", + "focus": [ + "auth", + "admin", + "bin", + "garage" + ], + "note": "Decap logs in via Gitea OAuth; site media resolves from Garage S3 shortcodes." + } + ] + }, + "components": [ + { + "id": "editors", + "type": "external", + "label": "Editors", + "sublabel": "Obsidian \u00b7 Neovim \u00b7 CLI", + "pos": [ + 40, + 300 + ], + "size": [ + 130, + 64 + ] + }, + { + "id": "admin", + "type": "frontend", + "label": "Decap CMS", + "sublabel": "admin.kontra.lab", + "pos": [ + 40, + 160 + ], + "size": [ + 130, + 64 + ] + }, + { + "id": "auth", + "type": "security", + "label": "Gitea OAuth", + "sublabel": "PKCE", + "pos": [ + 250, + 110 + ], + "size": [ + 130, + 56 + ], + "tag": "non-confidential" + }, + { + "id": "git", + "type": "cloud", + "label": "Gitea", + "sublabel": "sam/kontra-content", + "pos": [ + 250, + 300 + ], + "size": [ + 150, + 64 + ], + "tag": "origin of truth" + }, + { + "id": "pull", + "type": "backend", + "label": "Pull loop", + "sublabel": "git pull \u00b7 30s", + "pos": [ + 470, + 300 + ], + "size": [ + 140, + 64 + ] + }, + { + "id": "bin", + "type": "backend", + "label": "Go binary", + "sublabel": "templ + htmx + Goldmark", + "pos": [ + 680, + 300 + ], + "size": [ + 160, + 68 + ], + "tag": ":8600 Docker" + }, + { + "id": "garage", + "type": "cloud", + "label": "Garage S3", + "sublabel": "kontra-day bucket", + "pos": [ + 680, + 160 + ], + "size": [ + 150, + 64 + ] + }, + { + "id": "caddy", + "type": "cloud", + "label": "Caddy master", + "sublabel": ":80/:443 reverse proxy", + "pos": [ + 900, + 300 + ], + "size": [ + 150, + 64 + ] + }, + { + "id": "users", + "type": "external", + "label": "Readers", + "sublabel": "public web", + "pos": [ + 1120, + 300 + ], + "size": [ + 130, + 64 + ] + } + ], + "boundaries": [ + { + "kind": "region", + "label": "Editors (dev box .27)", + "wraps": [ + "editors", + "admin" + ] + }, + { + "kind": "region", + "label": "Gitea Docker (.35)", + "wraps": [ + "auth", + "git" + ] + }, + { + "kind": "region", + "label": "Kontra container (.13 :8600)", + "wraps": [ + "pull", + "bin", + "garage" + ] + }, + { + "kind": "region", + "label": "Edge (Caddy .35)", + "wraps": [ + "caddy", + "users" + ] + } + ], + "connections": [ + { + "id": "editors-git", + "from": "editors", + "to": "git", + "label": "git push", + "variant": "emphasis" + }, + { + "id": "admin-git", + "from": "admin", + "to": "git", + "label": "commit", + "fromSide": "bottom", + "toSide": "top", + "variant": "dashed" + }, + { + "id": "auth-login", + "from": "admin", + "to": "auth", + "label": "Login with Gitea", + "variant": "security", + "fromSide": "right", + "toSide": "bottom", + "labelAt": [ + 308, + 180 + ] + }, + { + "id": "auth-git", + "from": "auth", + "to": "git", + "label": "authorize", + "variant": "security", + "fromSide": "bottom", + "toSide": "left" + }, + { + "id": "git-pull", + "from": "git", + "to": "pull", + "label": "SSH", + "variant": "emphasis" + }, + { + "id": "pull-bin", + "from": "pull", + "to": "bin", + "label": "restart on change", + "labelAt": [ + 645, + 378 + ] + }, + { + "id": "bin-garage", + "from": "bin", + "to": "garage", + "label": "media shortcode", + "variant": "dashed", + "fromSide": "top", + "toSide": "bottom", + "labelDy": -58, + "labelAt": [ + 768, + 238 + ] + }, + { + "id": "bin-caddy", + "from": "bin", + "to": "caddy", + "label": "reverse_proxy :8600", + "labelAt": [ + 870, + 378 + ] + }, + { + "id": "caddy-users", + "from": "caddy", + "to": "users", + "label": "HTTPS", + "variant": "emphasis" + } + ], + "cards": [ + { + "dot": "emerald", + "title": "Publish path", + "items": [ + "Editors write markdown + front-matter in Obsidian/Neovim/CLI", + "All commits land on Gitea sam/kontra-content (origin)", + "Production container pulls every 30s and restarts on change" + ] + }, + { + "dot": "cyan", + "title": "Serving", + "items": [ + "Single static Go binary (templ + htmx + Tailwind tokens)", + "Goldmark renders markdown per request; SEO-friendly", + "Caddy master terminates HTTPS and proxies :8600" + ] + }, + { + "dot": "rose", + "title": "Auth & media", + "items": [ + "Decap CMS logs in with Gitea OAuth (PKCE)", + "Media resolves from Garage S3 via {{media:}} shortcodes" + ] + } + ] +} \ No newline at end of file diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 0000000..47ed80c --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,67 @@ +# Kontra — System Architecture + +A news-opinion site (GOTH stack: **Go + templ + htmx + Tailwind-tokens**) with markdown +content, a Decap CMS admin, Gitea-backed git deploy with automatic content sync, and +Garage S3 media — all on the home-lab network. + +> Interactive map: `docs/kontra-architecture-map.html` — also published at: +> https://maps.lab.audasmedia.com.au/kontra/docs/kontra-architecture-map.html + +## Diagram (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 +``` + +## Rendered image + +![Kontra architecture](diagrams/kontra-architecture.png) + +## Key facts + +| Layer | Where | What | +|---|---|---| +| Content origin | Gitea `.35:3001` | `sam/kontra-content` (markdown + front-matter) | +| App binary | Docker on `.13:8600` | Go (golang) + templ + htmx + Goldmark markdown | +| Content deploy | pull loop | `git pull` every 30s → restart server on change | +| Media | Garage S3 on `.13` | `kontra-day` bucket via `{{shortcode}}` | +| Admin | Decap CMS | at `admin.kontra.lab.audasmedia.com.au/web/admin/`, Gitea OAuth login | +| Reverse proxy | Caddy on `.35` | master :80/:443 → reverse_proxy `.13:8600` | +| Obsidian | `.27` vault | `obsidian/kontra_content` → content repo (git-sync) | + +## Mermaid source + +`docs/kontra-architecture.mmd` (single source of truth). Render: +`mmdc -i docs/kontra-architecture.mmd -o docs/diagrams/kontra-architecture.png --scale 2 -b white` \ No newline at end of file diff --git a/docs/diagrams/kontra-architecture.png b/docs/diagrams/kontra-architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..e41d814ae17b310e903f18784859ba434a04c25f GIT binary patch literal 76846 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 AtpET3 literal 0 HcmV?d00001 diff --git a/docs/diagrams/kontra-architecture.svg b/docs/diagrams/kontra-architecture.svg new file mode 100644 index 0000000..3b5cb5e --- /dev/null +++ b/docs/diagrams/kontra-architecture.svg @@ -0,0 +1 @@ +

Network

Media (Garage S3 on .13)

Kontra container (192.168.20.13 :8600 Docker)

Content Editors

git push

git push

OAuth + git commit

restart on change

SSH git

shortcode: media

emits HTML

reverse_proxy :8600

https

https

browser

browser

Gitea (192.168.20.35 :3001 Docker)

Obsidian vault
/obsidian/kontra_content

sam/kontra-content
markdown origin of truth

Neovim / CLI

Decap CMS
admin.kontra.lab

git pull loop
every 30s

Go binary
templ + htmx + Goldmark

Markdown render
front-matter + shortcodes

Garage S3
kontra-day bucket

Caddy master
.35 :80/:443

kontra.lab.audasmedia.com.au

admin.kontra.lab.audasmedia.com.au

\ No newline at end of file diff --git a/docs/kontra-architecture-map.html b/docs/kontra-architecture-map.html new file mode 100644 index 0000000..36d1456 --- /dev/null +++ b/docs/kontra-architecture-map.html @@ -0,0 +1,13705 @@ + + + + + + + Kontra — GOTH News-Opinion Site Diagram + + + + + + + + + + + +
+ +
+
+
+

Kontra — GOTH News-Opinion Site

+
+
+ + + + + + +
+ + Kontra — GOTH News-Opinion Site + A architecture diagram generated by Archify. + + + + + + + + + + + + + + + + + + + + + + + + + Editors (dev box .27) + + + Gitea Docker (.35) + + + Kontra container (.13 :8600) + + + Edge (Caddy .35) + + + + + + + + + + + + + + + Editors · Obsidian · Neovim · CLI · Editors (dev box .27) + + + + Editors + Obsidian · Neovim · CLI + + + + Decap CMS · admin.kontra.lab · Editors (dev box .27) + + + + Decap CMS + admin.kontra.lab + + + + Gitea OAuth · PKCE · Gitea Docker (.35) · non-confidential + + + + Gitea OAuth + PKCE + non-confidential + + + + Gitea · sam/kontra-content · Gitea Docker (.35) · origin of truth + + + + Gitea + sam/kontra-content + origin of truth + + + + Pull loop · git pull · 30s · Kontra container (.13 :8600) + + + + Pull loop + git pull · 30s + + + + Go binary · templ + htmx + Goldmark · Kontra container (.13 :8600) · :8600 Docker + + + + Go binary + templ + htmx + Goldmark + :8600 Docker + + + + Garage S3 · kontra-day bucket · Kontra container (.13 :8600) + + + + Garage S3 + kontra-day bucket + + + + Caddy master · :80/:443 reverse proxy · Edge (Caddy .35) + + + + Caddy master + :80/:443 reverse proxy + + + + Readers · public web · Edge (Caddy .35) + + + + Readers + public web + + + + + + git push + + + + commit + + + + Login with Gitea + + + + authorize + + + + SSH + + + + restart on change + + + + media shortcode + + + + reverse_proxy :8600 + + + + HTTPS + + + + + Legend + + + Frontend + + + + Backend + + + + Cloud + + + + Security + + + + External + + + +

+ + + + + + + + +
+ + +
+
+
+
+

Publish path

+
+
    +
  • • Editors write markdown + front-matter in Obsidian/Neovim/CLI
  • +
  • • All commits land on Gitea sam/kontra-content (origin)
  • +
  • • Production container pulls every 30s and restarts on change
  • +
+
+ +
+
+
+

Serving

+
+
    +
  • • Single static Go binary (templ + htmx + Tailwind tokens)
  • +
  • • Goldmark renders markdown per request; SEO-friendly
  • +
  • • Caddy master terminates HTTPS and proxies :8600
  • +
+
+ +
+
+
+

Auth & media

+
+
    +
  • • Decap CMS logs in with Gitea OAuth (PKCE)
  • +
  • • Media resolves from Garage S3 via {{media:}} shortcodes
  • +
+
+
+ +
+ + + + diff --git a/docs/kontra-architecture.mmd b/docs/kontra-architecture.mmd new file mode 100644 index 0000000..78cfbed --- /dev/null +++ b/docs/kontra-architecture.mmd @@ -0,0 +1,33 @@ +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 \ No newline at end of file