A bare-bones static site generator + web server in one.
posts/*.md→dist/posts/*.htmlmain.md→dist/index.html(the{{posts}}marker is replaced by the auto-generated post list)- LaTeX math (
$...$,$$...$$) is rendered to self-contained SVG at build time by RaTeX (no client JS, no CDN) - Empty
.mdfiles are skipped
cargo build --release
./target/release/website build # generate dist/
./target/release/website serve 8000 # build, then serve dist/ on 0.0.0.0:8000Writing a new post = drop a .md file in posts/ and re-run build. The title
comes from the post's first # heading; the URL slug is that title, lowercased
and hyphenated. (If a post has no # heading, the filename is used instead.)
Optionally add a line like ### date: 29 Nov, 2025; that date appears next to
the title on the main page, and parseable DD Mon, YYYY dates sort posts
newest-first before undated or unparseable posts.
# on the VPS, with the repo checked out and Rust installed:
cargo build --release
./target/release/website serve 80 # needs root/cap for port 80To keep it running, drop a systemd unit at /etc/systemd/system/website.service:
[Unit]
Description=website
After=network.target
[Service]
WorkingDirectory=/path/to/blog
ExecStart=/path/to/blog/target/release/website serve 8000
Restart=always
[Install]
WantedBy=multi-user.targetThen systemctl enable --now website. Put nginx/caddy in front for TLS, or
point it at port 8000 directly.
The systemd unit's ExecStart rebuilds from the latest checkout on every start,
so after pushing changes just restart the service on the VPS:
ssh <your-vps> 'systemctl restart website'its aliased as update-website
static/ is gitignored, so its files don't reach the VPS via git push. Use
the helper to rsync them up and restart the service (which rebuilds dist/):
./deploy-static.sh # uses the `website` ssh host alias
./deploy-static.sh other-host # or target a different hostWeb-bound images belong in static/ as already-compressed .webp (the server
maps the webp extension to image/webp); reference them from a post as
/<name>.webp.