From aa36b2905a39171e492109784c15acf8fd9923e5 Mon Sep 17 00:00:00 2001 From: claude-timemachine Date: Wed, 13 May 2026 00:52:53 +0200 Subject: [PATCH] initial: zero-trust markdown tutorials site MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- .gitignore | 12 + Dockerfile | 21 ++ Makefile | 19 ++ README.md | 92 ++++++ cmd/automc-tutorials/main.go | 63 ++++ content/cs/install-client/_index.md | 14 + content/cs/install-client/java-edition.md | 50 +++ content/cs/install-client/offline-client.md | 52 +++ content/en/install-client/_index.md | 14 + content/en/install-client/java-edition.md | 50 +++ content/en/install-client/offline-client.md | 52 +++ go.mod | 8 + go.sum | 6 + internal/content/library.go | 322 ++++++++++++++++++ internal/render/markdown.go | 35 ++ internal/server/server.go | 344 ++++++++++++++++++++ internal/server/static/search.js | 71 ++++ internal/server/static/style.css | 152 +++++++++ internal/server/templates/category.html | 18 + internal/server/templates/home.html | 17 + internal/server/templates/layout.html | 43 +++ internal/server/templates/page.html | 10 + 22 files changed, 1465 insertions(+) create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 Makefile create mode 100644 README.md create mode 100644 cmd/automc-tutorials/main.go create mode 100644 content/cs/install-client/_index.md create mode 100644 content/cs/install-client/java-edition.md create mode 100644 content/cs/install-client/offline-client.md create mode 100644 content/en/install-client/_index.md create mode 100644 content/en/install-client/java-edition.md create mode 100644 content/en/install-client/offline-client.md create mode 100644 go.mod create mode 100644 go.sum create mode 100644 internal/content/library.go create mode 100644 internal/render/markdown.go create mode 100644 internal/server/server.go create mode 100644 internal/server/static/search.js create mode 100644 internal/server/static/style.css create mode 100644 internal/server/templates/category.html create mode 100644 internal/server/templates/home.html create mode 100644 internal/server/templates/layout.html create mode 100644 internal/server/templates/page.html 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}}