Files
kontra/PLAN.md

11 KiB

Kontra Day — Phase One Plan

1. Goal

A news-opinion website ("Kontra Day") with an admin panel. Pages are mainly text with images, links, video. Subject areas in navigation. Admin can add/remove/change pages, set subject areas, control what's on the front page, and edit media. Hosted on the home network (192.168.20.13), exposed via Caddy. SEO-friendly.

2. Decisions (from conversation)

Decision Choice Why
Stack Go + templ + htmx + Alpine + Tailwind ("GOTH") Single static binary, server-rendered (SEO), no runtime deps. htmx for interactivity.
Content format Markdown (.md) + front-matter You write in never loses portability. Obsidian/Neovim native. Render via Goldmark (Go's CommonMark lib).
Admin Decap CMS (at admin.kontra.day) Free, open-source, browser-based UI that edits the same .md files directly and commits to git. No custom admin to build/maintain.
Content source of truth Git repo on Gitea (.35) History, rollback, multi-editor sync, off-site backup path. Matches your Gitea/Obsidian habits.
Templates templ components chosen per page (front-matter template:) See §6 — page-level → subject-level → site-level → theme fallback.
Media Garage S3 (on .13) referenced via {{media:...}} shortcodes Rename/migrate media without touching every article.
Colours CSS design tokens (CSS custom properties), admin-editable values, not code Per-subject accent colours as data, no template edits.
Deploy Git push → systemd timer git pull every 30s on .13 (no build step; Go renders on request) Instant content updates; .27 can be offline. Timer chosen over webhook (fewer moving parts).
Email phase (V) Revisit later — Go mail libs or hand off to network tooling Defer until requirements are real.

3. Topology (verified)

Machine IP Role Relevant bits
.27 sam-4screen-desktop 192.168.20.27 Dev box — Neovim + Obsidian Obsidian vault at /home/sam/obsidian/
.13 nixos-desktop 192.168.20.13 Web host, always-on Sites in /var/www/. Runs: Docker (Garage S3, Outline, PocketBase, family-home-lab stack), cluster of websites. Go binary + site lives here.
.35 sam-ubuntu1 192.168.20.35 Docker server Gitea (web :3001, SSH :2222) = repo origin. Caddy master reverse proxy (/Docker/Containers/caddy/). Multiple other containers.
.23 file-server 192.168.20.23 NFS file server Backup target (via .35 /mnt/nfs_usb_3tb).
Garage S3 — Object storage on .13 S3 API .13:3900, admin .13:3902, s3.lab.audasmedia.com.au. Web UI intended at garage-ui.lab.audasmedia.com.au.

Key service locations (verified from notes)

Service Where Note
Gitea Docker on .35 (gitea-server-1) web .35:3001, SSH .35:2222, gitea.lab.audasmedia.com.au
Caddy (master proxy) Docker on .35 Caddyfile in /Docker/Containers/caddy/. Routes *.lab.audasmedia.com.au + kontra.day
Garage S3 Docker on .13 /home/sam/deployment/garage/. Buckets per user + shared-media. v1.0.0
Outline Docker on .13:3000 outline.lab.audasmedia.com.au. Uses Garage S3 storage. REST API + MCP
Vikunja vikunja.lab.audasmedia.com.au Tasks + kanban. REST API .../api/v1. Token in ~/.config/environment.d/10-secrets.conf
Web files .13:/var/www/ NixOS users group → new folders need sudo chown -R sam:users /var/www/<folder>

4. File / storage layout

4.1 Content repo (single git repo, origin on Gitea .35)

Proposed repo: sam/kontra-day-content on Gitea.

kontra-day-content/
├── config/
│   ├── site.yaml              # site name, default template, nav, SEO defaults
│   ├── templates.yaml         # template list
│   └── frontpage.yaml         # front-page composition (blocks, order, featured)
├── subjects/                  # one dir per subject area
│   ├── politics/
│   │   ├── _subject.yaml      # name, slug, template default, accent colour
│   │   └── post-1.md
│   └── tech/
│       ├── _subject.yaml
│       └── article-2.md
├── articles/                  # (optional flat pool w/ subject in front-matter)
├── pages/                     # standalone pages (about, contact, 404)
└── media/                     # (optional local fallback; primary = Garage S3)

Front-matter per page:

---
title: My Opinion Piece
author: Sam
date: 2026-01-01
tags: [politics, tech]
template: article        # which template
subject: politics        # which subject area
image: {{media:cover.jpg}}
excerpt: A short summary...
published: true
publish_at: 2026-01-01   # optional scheduled publish
---
Body markdown here...

4.2 Distribution of copies

Machine Role Path
.35 (Gitea) Origin — single source of truth sam/kontra-day-content
.13 (web host) Production clone the Go binary serves /var/www/kontra_day/
.27 (dev) Authoring clone — Obsidian vault + Neovim /home/sam/obsidian/kontra_day/ (separate small vault)
  • Obsidian (DECIDED: option b): the content repo folder is a separate small Obsidian vault at /home/sam/obsidian/kontra_day/. It IS the kontra-day-content git repo → clean git-sync + deploy; main vault untouched. Obsidian's git-sync plugin pushes to Gitea automatically. User switches vaults in Obsidian when writing.
  • .13 never depends on .27 — it pulls from Gitea. Dev box can be offline; site stays live.

4.3 Sync / deploy flow

Editor on .27        Gitea on .35        Production on .13
Neovim / Obsidian ──push──▶  origin  ──webhook/poll──▶  git pull
   (clone)                    ▲                            │
                              │                            ▼
Decap (admin.kontra.day) ──commit──┘                 Go binary renders .md → HTML
  • Trigger (DECIDED): systemd timer git pull every 30s on .13. No webhook.
  • Obsidian git-sync plugin auto-syncs (commit+push) on .27 (separate kontra_day vault). Neovim saves → auto-commit via small file-watcher if desired.

5. Media (Garage S3, on .13)

  • Bucket: Garage on .13 (S3 API .13:3900, admin .13:3902). New dedicated bucket kontra-day (DECIDED) — do NOT rename/repurpose shared-media (other systems depend on it). New bucket gets its own access keys; media isolated, independently wiped/backed up.
  • Access: public reads via Caddy route (/media/* → bucket) rather than exposing Garage's port; private buckets can use presigned URLs. S3 root domain .s3.lab.audasmedia.com.au.
  • Admin shows media from Garage: Decap CMS media library (DECIDED).
  • Shortcodes in .md: {{media:cover.jpg}}, {{vimeo:12345}} → renderer resolves slug → actual URL at render time. Rename/migrate media without touching articles.
  • Reference notes: Garage Web UI — handoff (web UI at garage-ui.lab.audasmedia.com.au, khairul169/garage-webui matching Garage v1.x), credentials in .env (admin token GARAGE_ADMIN_TOKEN).

6. Templates & design control (where template choice lives)

Resolution order: page front-matter → subject _subject.yaml → site config → theme fallback.

Level Field Stored in
Page template: article front-matter
Subject template: photo-essay (default for its pages) subjects/x/_subject.yaml
Site default template config/site.yaml
Fallback base template theme folder
  • Front page: a special home template whose composition (blocks list: which subjects, order, featured stories) is an editable config (config/frontpage.yaml) — admin reorders/feeds blocks without touching code.
  • Colours: CSS custom properties (design tokens like --accent). Per-subject overrides via a data attribute. Admin edits token values, never templates.
  • New templates = code = a deploy step (fine; Phase 3).

7. Deploy config notes

  • Go binary runs in /var/www/kontra_day on .13 (per user decision).
  • Caddy on .35 is the master reverse proxy. It routes kontra.day → .13 Go binary, admin.kontra.day → Decap on .13.
  • .13 Nix config note: The user indicated a note needs adding to home.nix or configuration.nix on .13 for Caddy/site serving. (Likely a Caddyfile or reverse-proxy snippet on .13 to serve /var/www/kontra_day; the exact Nix file — configuration.nix for services vs home.nix — to be confirmed when the .13 Caddy/Nix layout is inspected in Phase 3.)
  • Permissions (.13): web folders use NixOS users group — new folders need sudo chown -R sam:users /var/www/kontra_day.
  • Current sites deploy via manual lsyncd .27→.13; this site uses git-based deploy instead (see §4.3). Retire lsyncd for this site.

8. Phase One deliverables (this plan + tasks)

  • This PLAN.md (decisions, topology, file layout)
  • Register tasks in Vikunja (projects, stages) and Outline (docs) — see PHASE ONE section
  • Confirm open items: exact Caddy/Nix file on .13, webhook vs timer, Decap media library approach, Garage bucket name
  • Phase 2 (design) kickoff when approved

9. Phases (from AGENT.md)

  1. Phase One — Planning: tech stack, content model, admin, storage layout ✅ (this file)
  2. Phase Two — Design: front-end + admin design options, demo images/templates
  3. Phase Three — Build & test: Go binary, templates, Decap admin, media, deploy
  4. Phase Four — Create Live: see system-architect skill when ready
  5. Phase Five — Link email & DNS: see Website Host Migration plan when ready
  6. Phase Six — Integration: console, backups
  7. Phase Seven — Marketing: populate website, marketing

10. Decisions & remaining open items

Decided (confirmed)

  • Garage bucket: new dedicated kontra-day (shared-media untouched)
  • Deploy trigger: systemd timer git pull every 30s on .13
  • Admin media: Decap CMS media library
  • Obsidian: separate small vault /home/sam/obsidian/kontra_day/ = the content repo

Remaining open items

  • Exact Nix file on .13 for Caddy/site (configuration.nix vs home.nix) — inspect in Phase 3
  • Whether to auto-commit Neovim saves via file-watcher (vs rely on git-sync in Obsidian + timer on .13)

Cross-references: AGENT.md; Obsidian notes: Garage Web UI — handoff, Backup Architecture — Offsite to AWS S3, Filesystem Drive Map, Dev-Ops Tooling — Mermaid/Archify/Vikunja/Outline, Docker Containers.