jwright 4a8552c408
build-and-deploy / build (push) Failing after 10m22s
images: remove generator watermark from all published art
The pale four-point star bottom-right in seven project covers and four blog
heroes is the image generator's watermark, not house style. Scrubbed via
delogo and verified numerically (peak luminance in the mark's box drops from
~100-255 to under 30 in every file); thumbnails regenerated from the cleaned
masters. The cover prompt file wrongly described the mark as part of the house
look and asked new generations to include one - corrected, with a reject-list
entry naming the mark and its usual position.
2026-08-25 18:00:38 +10:00
2026-08-05 02:02:11 +10:00

bztmon-site

Source for www.bztmon.com - portfolio and blog for a platform / infrastructure engineer. Static output, no server runtime, hardened by default.

This repo is public and lives on a self-hosted Gitea (git.bztmon.com), isolated from the private GitOps repos. The static site needs no secrets; none are committed.

Stack

  • Astro (static output), TypeScript, Tailwind v4
  • Static output with one self-hosted client bundle (~74 KB: Motion for scroll reveals, plus a small pre-paint theme script). No inline script anywhere, so script-src 'self' holds.
  • Content is visible with JavaScript disabled: reveal styles are gated behind an html.js class the script adds, and prefers-reduced-motion forces the visible state
  • Content and config are data-driven (src/data/, src/content/) - adding a post or 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)

Layout

src/
  content/blog/   posts (Markdown, zod-validated frontmatter)
  content/projects/ project dossiers
  data/           site.ts, series.ts, skills.ts, projects.ts, experience.ts
  components/     Hero, Nav, PostList, ProjectCard, ...
  layouts/        Layout.astro (SEO/OG, theme bootstrap)
  pages/          index, blog/, projects/, 404
  styles/         tokens.css (theme), global.css
scripts/          gen-og.mjs, build-image.sh, new-post.mjs
public/           heroes (blog/), diagrams (diagrams/), og.png, cv
nginx/            default.conf (security headers, caching) baked into the image
Dockerfile        Debian build stage -> nginx-unprivileged runtime

Posts

A post is a Markdown file in src/content/blog/ with zod-validated frontmatter; a malformed post fails the build, so a bad file never reaches production. scripts/new-post.mjs scaffolds one from flags or a JSON event on stdin - the seam a CI step can call. Each post belongs to one numbered series (src/data/series.ts), which drives the badge on the index cards.

CI/CD

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

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-merged - the deploy repo's main is branch-protected, and merging the PR is what triggers the Argo CD rollout. The runner holds least-privilege credentials only: a scoped deploy key, a PR token, and a registry push token.

  • npm run scan - build-time gate: no secrets, no inline scripts, no third-party origins in dist/
  • .audit-ci.json - fails the build on high/critical advisories
  • renovate.json - keeps npm deps and the digest-pinned base images current

Deploy

Built into a container image, served by nginx-unprivileged on Kubernetes, exposed outbound-only via Cloudflare Tunnel. The image is pinned by digest in the private deploy repo and rolled out by Argo CD. Manual bootstrap build: scripts/build-image.sh push. Full posture in SECURITY.md.

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%