commit aa36b2905a39171e492109784c15acf8fd9923e5 Author: claude-timemachine Date: Wed May 13 00:52:53 2026 +0200 initial: zero-trust markdown tutorials site Single-binary Go service that renders markdown pages from a runtime volume mount. Targeted at public, no-auth, no-WAF deployment behind a TLS ingress; security posture is defense-in-depth at every layer: - goldmark with no WithUnsafe — raw HTML in author markdown is stripped - CSP without 'unsafe-inline', plus HSTS, COOP, CORP, Permissions-Policy - static handler rejects non-GET/HEAD, directory listings, dotfiles, traversal - content loader rejects symlinks that escape the content root, dotfiles, and .md files larger than 1 MiB - per-page template trees (cloned from layout) so define-blocks don't collide between home/category/page - SIGHUP triggers atomic library swap — live edits on volume, no rebuild Locale layout content///.md. Categories without _index.md still appear on the home page with a humanized name. Search is a ~70-line vanilla JS scan over /search.json?lang=; swap for a real indexer if the corpus ever balloons. Co-Authored-By: Claude Opus 4.7 (1M context) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d6871c1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +# compiled binary (root only — don't shadow cmd/automc-tutorials/) +/automc-tutorials + +# test artifacts +*.test +*.out +coverage.txt + +# editor + os +.vscode/ +.idea/ +.DS_Store diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..4192e9c --- /dev/null +++ b/Dockerfile @@ -0,0 +1,21 @@ +# --- build stage --- +FROM golang:1.25-alpine AS build +WORKDIR /src +COPY go.mod go.sum ./ +RUN go mod download +COPY . . +RUN CGO_ENABLED=0 GOOS=linux go build -trimpath -ldflags='-s -w' \ + -o /out/automc-tutorials ./cmd/automc-tutorials + +# --- runtime stage --- +# Image carries only the binary. Content is provided at runtime via a volume +# mount at /content (typically a Longhorn RWX PVC populated by `git pull` or +# rsync from an admin shell). Live edits + SIGHUP reload = no rebuild needed. +FROM gcr.io/distroless/static-debian12:nonroot +WORKDIR /app +COPY --from=build /out/automc-tutorials /app/automc-tutorials +EXPOSE 8080 +ENV ADDR=:8080 CONTENT_DIR=/content DEFAULT_LOCALE=en +VOLUME ["/content"] +USER 65532:65532 +ENTRYPOINT ["/app/automc-tutorials"] diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..2ea111d --- /dev/null +++ b/Makefile @@ -0,0 +1,19 @@ +.PHONY: build run dev test vet docker + +build: + go build -o automc-tutorials ./cmd/automc-tutorials + +run: build + ./automc-tutorials + +dev: + go run ./cmd/automc-tutorials + +test: + go test ./... + +vet: + go vet ./... + +docker: + docker build -t git.timemachine.center/timemachine/automc-tutorials:latest . diff --git a/README.md b/README.md new file mode 100644 index 0000000..beb114c --- /dev/null +++ b/README.md @@ -0,0 +1,92 @@ +# automc-tutorials + +Public-facing tutorials site for the automc Minecraft platform. Renders markdown from a mounted volume as a localized static-ish web app. No auth, no database, no state — exposed to the open internet behind TLS, hardened for zero-trust. + +## Stack + +- **Go** + `net/http` + `html/template` — single binary +- **goldmark** — markdown → HTML (GFM + footnotes + auto heading IDs) +- **Plain CSS** — no framework, dark mode via `prefers-color-scheme` +- **Vanilla JS** — ~70-line client-side substring search, no dependency +- Content lives on a mounted volume; reload via SIGHUP + +## Layout + +``` +cmd/automc-tutorials/main.go — entry: flags, signals, SIGHUP reload +internal/content/ — loader + path-traversal guard for content///.md +internal/render/ — goldmark wrapper (HTML escaping on) +internal/server/ — mux, handlers, embedded templates + /static/ bundle +content/// — markdown source. _index.md is the category landing page (optional). +``` + +## Adding or editing a tutorial + +The image carries only the binary. Markdown sits on a volume mounted at `/content` (default in the image; override with `CONTENT_DIR`). Workflow: + +1. Open an admin shell on the host that owns the PVC (or shell into a pod that mounts the same RWX volume). +2. Drop / edit `.md` files under `///`. +3. Add YAML frontmatter: + + ```yaml + --- + title: Page title + summary: One-sentence preview shown on category list. + order: 1 + --- + ``` + +4. Write markdown. GFM tables, fenced code, footnotes, autolinks, task lists all work. Raw HTML in markdown is **escaped** — don't try to embed ` + +{{end}} diff --git a/internal/server/templates/page.html b/internal/server/templates/page.html new file mode 100644 index 0000000..e7c97d1 --- /dev/null +++ b/internal/server/templates/page.html @@ -0,0 +1,10 @@ +{{define "title"}}{{.Page.Title}} · automc tutorials{{end}} +{{define "content"}} +
+ +
{{.BodyHTML}}
+
+{{end}}