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]
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -9,3 +9,6 @@ app/src/*_templ.go
|
||||
.env
|
||||
.env.*
|
||||
kontra-bin
|
||||
|
||||
# archify visual-check sidecars (evidence, not source)
|
||||
docs/*.visual-check.*
|
||||
|
||||
@@ -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 <!-- same-turn-2026-09-08 21:22:57 -->
|
||||
|
||||
- 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]]
|
||||
@@ -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]]
|
||||
@@ -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/<slug>/_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]]
|
||||
@@ -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 <!-- same-turn-2026-09-08 21:22:57 -->
|
||||
|
||||
- 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]]
|
||||
@@ -10,7 +10,8 @@
|
||||
"skills/obsidian-cli",
|
||||
"skills/npm-security",
|
||||
"skills/system-architect",
|
||||
"skills/design-explore"
|
||||
"skills/design-explore",
|
||||
"skills/project-diagramming-mermaid"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
@@ -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": []
|
||||
}
|
||||
170
README.md
Normal file
170
README.md
Normal file
@@ -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)
|
||||
|
||||

|
||||
|
||||
```mermaid
|
||||
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 (192.168.20.35 :3001 Docker)"]
|
||||
G[(sam/kontra-content<br/>markdown origin of truth)]
|
||||
end
|
||||
|
||||
subgraph prod["Kontra container (192.168.20.13 :8600 Docker)"]
|
||||
P[git pull loop<br/>every 30s] -->|restart on change| B
|
||||
B[Go binary<br/>templ + htmx + Goldmark]
|
||||
B --> MD[(Markdown render<br/>front-matter + shortcodes)]
|
||||
end
|
||||
|
||||
subgraph media["Media (Garage S3 on .13)"]
|
||||
S3[(Garage S3<br/>kontra-day bucket)]
|
||||
end
|
||||
|
||||
G -->|"SSH git"| P
|
||||
B -->|"shortcode: media"| S3
|
||||
B -->|"emits HTML"| CB[Caddy master<br/>.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/<slug>/… # 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.*
|
||||
317
docs/archify/kontra-architecture.architecture.json
Normal file
317
docs/archify/kontra-architecture.architecture.json
Normal file
@@ -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"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
67
docs/architecture.md
Normal file
67
docs/architecture.md
Normal file
@@ -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<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 (192.168.20.35 :3001 Docker)"]
|
||||
G[(sam/kontra-content<br/>markdown origin of truth)]
|
||||
end
|
||||
|
||||
subgraph prod["Kontra container (192.168.20.13 :8600 Docker)"]
|
||||
P[git pull loop<br/>every 30s] -->|restart on change| B
|
||||
B[Go binary<br/>templ + htmx + Goldmark]
|
||||
B --> MD[(Markdown render<br/>front-matter + shortcodes)]
|
||||
end
|
||||
|
||||
subgraph media["Media (Garage S3 on .13)"]
|
||||
S3[(Garage S3<br/>kontra-day bucket)]
|
||||
end
|
||||
|
||||
G -->|"SSH git"| P
|
||||
B -->|"shortcode: media"| S3
|
||||
B -->|"emits HTML"| CB[Caddy master<br/>.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
|
||||
|
||||

|
||||
|
||||
## 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`
|
||||
BIN
docs/diagrams/kontra-architecture.png
Normal file
BIN
docs/diagrams/kontra-architecture.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 75 KiB |
1
docs/diagrams/kontra-architecture.svg
Normal file
1
docs/diagrams/kontra-architecture.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 34 KiB |
13705
docs/kontra-architecture-map.html
Normal file
13705
docs/kontra-architecture-map.html
Normal file
File diff suppressed because it is too large
Load Diff
33
docs/kontra-architecture.mmd
Normal file
33
docs/kontra-architecture.mmd
Normal file
@@ -0,0 +1,33 @@
|
||||
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 (192.168.20.35 :3001 Docker)"]
|
||||
G[(sam/kontra-content<br/>markdown origin of truth)]
|
||||
end
|
||||
|
||||
subgraph prod["Kontra container (192.168.20.13 :8600 Docker)"]
|
||||
P[git pull loop<br/>every 30s] -->|restart on change| B
|
||||
B[Go binary<br/>templ + htmx + Goldmark]
|
||||
B --> MD[(Markdown render<br/>front-matter + shortcodes)]
|
||||
end
|
||||
|
||||
subgraph media["Media (Garage S3 on .13)"]
|
||||
S3[(Garage S3<br/>kontra-day bucket)]
|
||||
end
|
||||
|
||||
G -->|"SSH git"| P
|
||||
B -->|"shortcode: media"| S3
|
||||
B -->|"emits HTML"| CB[Caddy master<br/>.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
|
||||
Reference in New Issue
Block a user