This repository contains the source files for levineuwirth.org in their entirety and is automatically updated whenever the website is rebuilt. https://levineuwirth.org
Go to file
Levi Neuwirth 0d0794fbf0
infra: track the Forgejo compose file
The only description of how git.levineuwirth.org is deployed lived on the
VPS itself, which meant recovering the deployment required first recovering
the machine — or unpacking a backup tarball to read its own compose file.
It belongs with nginx/ and systemd/.

Recorded in the header rather than lost to shell history: that DOMAIN,
SSH_DOMAIN and ROOT_URL were placeholder text until today; that
FORGEJO__* variables reach app.ini on container recreation and not on
restart; that 3000 is bound to loopback on purpose; and that SSH on 2222,
while genuinely open on the host, is filtered by enough public networks to
be the convenience path rather than the dependable one.

Also removes the caddy container and its leftovers from that box (not
tracked here, but the reason belongs with this change): created at initial
setup in March, never started once — it wanted host ports 80 and 443,
which nginx already held. Its Caddyfile did exactly what nginx now does,
and its data directories were empty. It carried restart=unless-stopped
next to a stale compose backup, so the only thing it could still plausibly
do was win a race for 443 against the server actually serving the site.

NO_REPLY_ADDRESS is set here too, now that checking showed nothing depends
on the old value: the account does not hide its email and no address in the
database sits on a noreply domain, so there was no attribution to orphan.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SUGesXiMmACsLBTGG1xuEU
2026-08-11 13:49:30 +02:00
archive Archive: close out the audit-pass refinements 2026-06-19 16:23:14 -04:00
build vita: fix the entry layout, which was losing to typography.css 2026-08-11 09:39:22 +02:00
content vita: generate the project index, and add a web_visible axis 2026-08-11 09:07:38 +02:00
data cv: August 2026 refresh across CV, résumé, and site 2026-08-10 23:40:14 +02:00
forgejo infra: track the Forgejo compose file 2026-08-11 13:49:30 +02:00
nginx Rich reference popups: arXiv lead figures, prominent Wikipedia images 2026-06-10 13:02:15 -04:00
paper sync before transfer 2026-07-27 19:10:51 -04:00
static vita: roman venue and role lines, and stop chips restating the venue 2026-08-11 09:51:29 +02:00
systemd infra: forgejo sync tooling and a nightly backup timer 2026-08-11 13:37:34 +02:00
templates august fixes 2026-08-11 09:08:41 +02:00
tests sync before transfer 2026-07-27 19:10:51 -04:00
tools infra: forgejo sync tooling and a nightly backup timer 2026-08-11 13:37:34 +02:00
yaml-source vita: roman venue and role lines, and stop chips restating the venue 2026-08-11 09:51:29 +02:00
.env.example States/Context/Embeddings fixes 2026-04-26 11:22:57 -04:00
.gitignore chore: track CV/résumé pipeline source 2026-08-10 23:40:01 +02:00
.python-version GPG signing, embedding pipeline, visualization filter, search timing, sig popups 2026-03-20 20:14:49 -04:00
ARCHIVE.md Archive: close out the audit-pass refinements 2026-06-19 16:23:14 -04:00
AUDIT-2026-06-09.md Add 2026-06-09 repository audit findings 2026-06-09 18:57:43 -04:00
AUDIT.md Add AUDIT.md 2026-05-07 15:07:48 -04:00
LICENSE Add MIT License to the project 2026-03-15 19:02:33 +00:00
MARKS.md Marks I 2026-05-07 23:51:14 -04:00
Makefile sync before transfer 2026-07-27 19:10:51 -04:00
PHOTOGRAPHY.md Spec dilemma 2026-05-01 21:22:01 -04:00
README.md Docs: align WRITING.md and README with the implementation 2026-06-10 09:43:25 -04:00
WRITING.md august fixes 2026-08-11 09:08:41 +02:00
cabal.project initial deploy! whoop 2026-03-17 21:56:14 -04:00
cabal.project.freeze august fixes 2026-08-11 09:08:41 +02:00
levineuwirth.cabal vita: generate /about from the CV data instead of hand-copying it 2026-08-11 08:40:33 +02:00
pyproject.toml Last audit stragglers: scaffolder, refreeze safety, atomic-write polish 2026-06-10 11:43:14 -04:00
uv.lock Last audit stragglers: scaffolder, refreeze safety, atomic-write polish 2026-06-10 11:43:14 -04:00

README.md

levineuwirth.org

Personal site of Levi Neuwirth — essays, blog posts, poetry, fiction, and music. Built with Hakyll and Pandoc, with a custom build system in build/ and a Haskell + JS + Python toolchain.

Quickstart

make build              # one-shot production build into _site/
make dev                # dev build (drafts visible) + local server on :8000
make watch              # Hakyll live-reload dev server (drafts visible)
make clean              # cabal run site -- clean
make deploy             # clean → build → sign → push → rsync to VPS

make build always runs make clean implicitly when invoked from make deploy. For day-to-day work, prefer make dev (which serves the site on http://localhost:8000) or make watch (Hakyll's live-reload preview server, which rebuilds on save and serves the site locally).

Run make build any time you add or replace binary assets (JPEG/PNG figures, PDFs, music assets). make dev and make watch skip the convert-images.sh / pdf-thumbs preprocessing steps, so a fresh JPEG will have no .webp companion and a fresh PDF will have no thumbnail until a full make build regenerates them. Once the companions exist they survive subsequent make dev runs.

Optional features

  • Similar-links and embeddings. tools/embed.py precomputes page-level embeddings for the "Related" block. To enable:

    uv sync                 # creates .venv with sentence-transformers, faiss-cpu
    

    The build silently skips embedding when .venv is absent.

  • Client-side semantic search. Downloads a quantized ONNX model used by static/js/semantic-search.js (run once; files are gitignored):

    make download-model
    
  • Image conversion. make build calls tools/convert-images.sh to produce .webp companions next to every JPEG/PNG. Requires cwebp (libwebp on Arch, webp on Debian/Ubuntu).

  • PDF thumbnails. make pdf-thumbs generates first-page thumbnails for PDFs in static/papers/ using pdftoppm (poppler on Arch, poppler-utils on Debian/Ubuntu). Skipped silently when missing.

Configuration

.env (gitignored, copy from .env.example) holds the GitHub PAT and the VPS rsync target consumed by make deploy. Never commit it.

Repository layout

  • build/ — Haskell build system (Hakyll rules, Pandoc filters, contexts). See build/Filters/ for the Pandoc AST transforms (sidenotes, wikilinks, transclusion, score embedding, viz, …).
  • content/ — authored Markdown (essays, blog, poetry, fiction, music).
  • templates/ — Hakyll/Pandoc HTML templates.
  • static/ — CSS, JS, fonts, images, vendored PDF.js.
  • tools/ — Python tooling (embeddings, importers) and shell scripts.
  • data/ — generated and source data (commonplace.yaml, annotations.json, bibliographies, similar-links.json).
  • nginx/ — vhost snippets shipped to the VPS (security-headers.conf, static-assets.conf, popup-proxy.conf). The live vhost on the VPS is the source of truth; see nginx/vhost.conf.example for the canonical structure and the include order these snippets expect.

Architecture pointers

  • build/Site.hs is the Hakyll rules entry point.
  • build/Patterns.hs defines canonical content patterns shared by Backlinks, Authors, Tags, and Site.
  • build/Compilers.hs wires the Pandoc filter chain into Hakyll.
  • build/Filters/Images.hs does WebP <picture> wrapping; requires the .webp companions produced by tools/convert-images.sh.

License

See LICENSE.