Files
bztmon-site/README.md
T

80 lines
3.0 KiB
Markdown

# bztmon-site
Source for [www.bztmon.com](https://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
- Zero JS by default; small islands for the theme toggle and scroll reveals
- Content and config are data-driven (`src/data/`, `src/content/`) - adding a
post or project never touches a component
## Develop
```bash
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 ArgoCD 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 ArgoCD. Manual bootstrap build:
`scripts/build-image.sh push`. Full posture in `SECURITY.md`.