jwright 6ea2582a66 blog: heroes belong in frontmatter - fix the five new posts
The five carried the hero only as an inline markdown image, but the index
cards and the post hero figure render from hero/heroAlt frontmatter - so
the cards showed no image and the post pages lost the hero treatment.
Moved each hero into frontmatter and dropped the inline line (the body
keeps only the diagram), matching every earlier post.
2026-08-03 08:24:22 +10:00
2026-06-17 17:20:54 +10:00

bztmon-site

The source for www.bztmon.com — Jonathon Wright's portfolio / résumé site. A fast, animated, security-hardened static site for a platform / infrastructure engineer.

This repo is public. It lives on a self-hosted public Gitea (git.bztmon.com), isolated from the private homelab GitOps. Never commit secrets — the static site needs none.

Stack

  • Astro (static output) + TypeScript + Tailwind v4
  • Zero JS by default; tiny islands for the theme toggle + scroll reveals
  • Content & config are data-driven (src/data/) — adding a project never touches a component

Develop

npm install
npm run dev        # http://localhost:4321
npm run check      # astro check (types + diagnostics)
npm run build      # static build → dist/
npm run preview    # serve the build locally
npm run gen:og     # regenerate the social-preview image (public/og.png)

Project layout

src/
  data/          site.ts, socials.ts, skills.ts, projects.ts, experience.ts
  components/     Hero, Nav, ThemeToggle, ProjectCard, SkillGroup, ...
  layouts/        Layout.astro (SEO/OG, theme bootstrap)
  pages/          index.astro, projects/, 404.astro
  styles/         tokens.css (theme), global.css
  lib/            build-time helpers (cv detection)
scripts/          gen-og.mjs, build-image.sh
nginx/            default.conf (security headers, caching) baked into the image
Dockerfile        Debian build stage → nginx-unprivileged runtime

Content TODOs (Jonathon)

  • Drop a real CV at public/cv.pdf — the Download CV button appears automatically.
  • Fill the TODO(Jonathon) markers in src/data/experience.ts, projects.ts, socials.ts (employer names, dates, GitHub/LinkedIn handles).

Publishing a post

A post is just a Markdown file in src/content/blog/. Write one by hand, or generate a schema-valid one with the publish helper (this is the seam an IaC/CI step calls):

# from flags
node scripts/new-post.mjs --title "My post" --summary "One line" \
     --tags "kubernetes,gpu" [--draft] [--bodyFile notes.md]

# from a JSON event (e.g. an Ansible/AWX deploy summary)
echo '{"title":"...","summary":"...","tags":["x"],"body":"## Hi\n..."}' \
  | node scripts/new-post.mjs --stdin

Commit the file to main → CI rebuilds and ships. A malformed post fails the build (frontmatter is zod-validated), so a bad pipeline event never reaches production.

CI/CD

.gitea/workflows/deploy.yml runs on a self-hosted runner (a dedicated unprivileged user on the bastion):

npm ci → astro check → audit-ci (high/critical gate) → build → scan dist →
SBOM (CycloneDX) → buildah build+push → open a digest-bump PR to home-ops

The PR is never auto-mergedhome-ops main is branch-protected; merging it is what triggers the ArgoCD rollout. The runner holds only least-privilege creds (a home-ops-scoped deploy key + a PR token + a registry push token).

  • npm run scan — build-time gate: no secrets, no inline scripts, no third-party origins.
  • .audit-ci.json — fails on high/critical advisories. One allowlisted: GHSA-gv7w-rqvm-qjhr (esbuild install-integrity; build-time only, mitigated by the committed lockfile + trusted registry).
  • renovate.json — keeps npm deps and the digest-pinned base images current.

Deploy

Built into a container image, served by nginx-unprivileged on a homelab Kubernetes cluster, exposed via Cloudflare Tunnel. The image is pinned by digest in the private home-ops repo and rolled out by ArgoCD. Manual/bootstrap build: scripts/build-image.sh push. See SECURITY.md for the full security posture.

S
Description
Source for www.bztmon.com
Readme 8.2 MiB
Languages
Astro 57%
TypeScript 14.8%
CSS 11.8%
JavaScript 10.3%
Shell 5.1%
Other 1%