- Rust 72.7%
- CSS 25.7%
- Dockerfile 1.6%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| content | ||
| docs/adr | ||
| firmware | ||
| src | ||
| static | ||
| .gitignore | ||
| Caddyfile | ||
| Cargo.lock | ||
| Cargo.toml | ||
| docker-compose.yml | ||
| Dockerfile | ||
| README.md | ||
Critterscan website
Server-rendered site for the Critter Scanner, an open-source ESP32 critter scanner: documentation, firmware downloads with checksums, an in-browser flasher, and a news/build log. Built in Rust (axum + maud), fronted by Caddy, deployed with Docker Compose.
It emits plain HTML with no third-party resources — no CDN, no web fonts, no
trackers, no cookies. The one bit of client-side JavaScript is the optional
in-browser firmware flasher on /flash, and it's progressive enhancement: the
command-line method is always shown and needs no JS (ADR-0001).
Heads up: this code was written and reviewed but not compiled in the environment it was generated in (no Rust toolchain there). It's structured to build cleanly; run
cargo build/docker compose buildlocally and fix any environment-specific issues before deploying.
Quick start (Docker)
docker compose up --build
# then open http://localhost
For production, set your domain so Caddy provisions HTTPS automatically:
cp .env.example .env # edit SITE_ADDRESS and BASE_URL
SITE_ADDRESS=critterscan.example BASE_URL=https://critterscan.example \
docker compose up --build -d
Local development (no Docker)
cargo run
# serves on http://localhost:8080, using ./content, ./firmware, ./static
Environment variables (all optional): BIND_ADDR (default 0.0.0.0:8080),
CONTENT_DIR (content), FIRMWARE_DIR (firmware), STATIC_DIR (static),
BASE_URL (http://localhost, used for RSS links), RUST_LOG (info).
Building
cargo build --release # binary at target/release/critterscan-web
docker compose build
Commit the generated Cargo.lock for reproducible builds.
Project layout
src/
main.rs routes (incl. /flash), state, static/download serving, shutdown
config.rs site identity + external links (edit this)
content.rs markdown loading, front matter, sanitize, mtime cache
firmware.rs scan release dirs, checksums, signatures
feed.rs RSS 2.0 feed
views.rs maud HTML views (structure), incl. the flasher page
static/
style.css the whole design; hand-written, no framework
emoji/ self-hosted neofox art (logo, mascot, flasher) — see emoji/README.md
board/ front/back renders + STEP model — see board/README.md
vendor/ vendored esp-web-tools (no CDN) — see vendor/README.md
content/
docs/ documentation (markdown) — includes the merged flashing guide
news/ dated posts (markdown)
pages/ impressum.md, datenschutz.md
firmware/
v<version>/ one folder per release + optional manifest.json (see firmware/README.md)
docs/adr/ architecture decision records (kept with the repo)
Dockerfile docker-compose.yml Caddyfile
Adding content
Docs and news are the same pipeline — drop a markdown file in and it appears on the next request, no rebuild. Front matter example (news):
---
title: My update
date: 2026-10-01
summary: One line shown in the list and RSS.
tags: announcement, hardware
---
Body in **markdown**. Tables, footnotes and strikethrough are enabled.
The slug drops a leading YYYY-MM-DD-. Docs work the same way in content/docs/
(a date isn't required).
Firmware & the browser flasher
Firmware releases live under firmware/v<version>/ — see
firmware/README.md for the layout, checksums, and how to
build the merged factory image + manifest.json the web flasher needs.
The /flash page uses esp-web-tools
over Web Serial, vendored locally (v10.4.0, in static/vendor/esp-web-tools/)
to honour the no-CDN rule — no setup needed. esp-web-tools itself handles the
supported / unsupported / not-secure states, so browsers without Web Serial
(Safari, stable Firefox) are shown a message pointing at the command-line method.
Browser support for the flasher: Chrome, Edge, and Opera on desktop over HTTPS (or
localhost). To actually complete a flash you need a real merged factory image +
manifest.json (see firmware/README.md); the sample release
ships a placeholder, so the flow will prompt for a port but has nothing real to write
until you add a build.
Customizing
- Identity & links:
src/config.rs— name, tagline, repo, social, donation URL, contact email. - Legal pages:
content/pages/impressum.mdanddatenschutz.md(bracketed placeholders + a "not legal advice" note). - Design: everything visual is in
static/style.css, driven by CSS custom properties (warm light + dark themes, neofox-orange accent) at the top. The logo, mascot, and flasher art are the neofox PNGs instatic/emoji/. - Critter emoji (neofox): self-host with attribution — see
static/emoji/README.md. The footer credit is required (CC BY-NC-SA 4.0).
What's intentionally stubbed (see docs/adr/)
- Login (ADR-0007) — the "Log in" button leads to a placeholder page.
- Interactive 3D viewer (ADR-0008) — evaluated and dropped; the front/back image gallery (front shown, back on demand) proved enough. Board images and the STEP download ship.
- GPG signatures (ADR-0005) — the firmware page shows
pendinguntil the signing workflow exists; checksums ship now.
Client-side JS is limited to one sanctioned, progressive-enhancement exception
(ADR-0001): the /flash browser flasher. The board front/back gallery is pure CSS.
License
Dual-licensed under MIT or Apache-2.0, at your option. Neofox emoji art (if added) is CC BY-NC-SA 4.0 by Volpeon and is not covered by this project's license.
ToDo
- security scan/ hardening
- include Firmware
- signing