A directory of websites, apps and tools built with Rust. Itself built with Rust.
- Axum for HTTP, Maud for compile-time-checked HTML, SQLite via sqlx with compile-time-checked SQL.
- Every public page is rendered once, pre-compressed (brotli/gzip) and served from memory with an ETag. Any catalog write purges the cache.
- One static binary, embedded assets and migrations. Nothing to install next to it besides the SQLite file (and optionally Litestream for backups).
- Public submission form with review queue, bearer-token admin JSON API for agents, Stripe-powered "featured" slots, PostHog analytics.
cp .env.example .env # set ADMIN_TOKEN at least
export DATABASE_URL=sqlite://data/dev.db?mode=rwc
cargo run -- migrate
cargo run -- seed # loads seed/projects.json
cargo run # http://localhost:3000Tests: cargo test. Load test: scripts/loadtest.sh (needs oha).
Everything else, including the admin API reference, Stripe and PostHog setup and the rules for changing the code, is in AGENTS.md.
Production runs on CapRover at https://builtwithrust.com. Pushes to main
run the Rust checks, build an image tagged with the commit SHA in
ghcr.io/lvtd-llc/builtwithrust, and deploy it using the app's deployment token.
The workflow can also be run manually from main.
It waits until /healthz reports the deployed commit in its
X-Deployment-Revision header before marking the rollout successful.
GitHub Actions secrets: CAPROVER_SERVER (dashboard origin) and APP_TOKEN
(the builtwithrust app token). Image publishing uses GITHUB_TOKEN.
CapRover must have permission to pull the image from GHCR.
CapRover settings:
- Container HTTP port:
3000; one instance pinned to its storage node. - Persistent volume
builtwithrust-datamounted at/data. SITE_URL=https://builtwithrust.comand a generatedADMIN_TOKEN.DATABASE_URL=sqlite:///data/builtwithrust.db?mode=rwc(the image default).- PostHog settings from
.env.example; Stripe stays disabled until configured. - Custom domain
builtwithrust.com, HTTPS enabled, and force HTTPS enabled.
Migrations run automatically at startup. Seed a fresh deployment once with
builtwithrust seed inside the running container, then purge the page cache
through the admin API. Never seed automatically on deploy: it would overwrite
catalog edits. The runtime uses UID 10001, which must own /data.
To roll back, deploy a previous commit's image tag through CapRover. The
SQLite volume survives deployments; schema migrations may require a separate
database recovery. Persistent storage is not a backup; litestream.yml
provides a starting point for configuring backups separately.
MIT
The root /{key}.txt route serves indexnow-key.txt as plain text. This is
an intentionally public ownership-verification value, not an admin credential.
No environment secrets or paid API are required.
The IndexNow Actions workflow checks the public sitemap hourly and after a successful production deployment. It submits new/modified URLs and URLs removed since the last accepted snapshot (including unpublished/deleted projects). Full RFC3339 sitemap timestamps detect same-day listing edits. On any catalog change, all current sitemap URLs are refreshed too: directory/category pages and related-project cards may have changed. Successful deployments force a refresh to cover template/copy changes without database timestamp changes. Unchanged hourly runs make no submissions. GitHub schedules can be delayed.
Snapshots use Actions cache, serialized across runs; they advance only when all batches return HTTP 200 or 202. Errors fail the workflow and the next run retries. An evicted/missing cache bootstraps all current URLs but cannot recover previously removed URLs. Edits made and reverted entirely between polls are not observed. For a missed deletion, submit the removed URL explicitly using the IndexNow API. HTTP 202 means ownership validation is pending; neither 200 nor 202 guarantees indexing. Submission is shared among participating engines, not Google.
Run manually using gh workflow run indexnow.yml, or locally with
python scripts/indexnow.py --snapshot /tmp/bwr-indexnow.json. Add --force
for a deliberate full refresh. Protocol: https://www.indexnow.org/documentation.