# 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** | **Git files beside each article** (`{{media:/}}`) — Garage S3 dropped 2026-09-10 | Robust, versioned, backed up with content; no object-store auth/sync. | | **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/` | ## 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: ```yaml --- 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.*