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.
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.jsclass the script adds, andprefers-reduced-motionforces 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 indist/.audit-ci.json- fails the build on high/critical advisoriesrenovate.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.