repos
/ analytics-rust master

analytics-rust

mirror archived upstream

Single-binary self-hosted website analytics on Rust axum: collector API, dashboards, world map, and PDF reports.

analyticsaxumdockerrustself-hostedsqliteviteweb-analytics

5.7 KB · 118 lines · markdown Raw History
  1> **Archived.** This project is no longer in use and no longer maintained. Last updated July 2026.
  2
  3# Analytics
  4
  5Self-hosted, privacy-respecting website analytics for a single operator.
  6
  7This exists to answer "how is my site doing?" without handing visitor data to a third party or paying a SaaS bill. You drop a small collector script onto any number of sites you own, and this app collects the events and renders a dashboard: metric cards, time-series charts, a world map, and exportable reports. It is one small axum binary with a SQLite database, so the whole thing runs from a single container with no external services.
  8
  9It began as a Django app; the data was migrated into this Rust rewrite, which was feature-complete against the original.
 10
 11## Features
 12
 13- Embed a small collector script on any number of properties (tracked sites)
 14- Tracks page views, clicks, scrolls, sessions, and custom events
 15- Dashboard with 16 metric/chart/list aggregations and user-defined custom cards
 16- World map with per-country admin-1 drill-down on click
 17- GeoIP enrichment (auto-downloads DB-IP City Lite) and user-agent parsing (auto-downloads ua-parser regexes)
 18- Bot traffic routed to a separate table so human dashboards never have to filter it
 19- Public/private toggle per property; signed-cookie auth for the operator
 20- PDF and markdown export of any dashboard view (PDF rendered in-process via embedded Typst, no chromium)
 21- Self-tracking: the app tracks its own usage via an auto-created "Proprium" property
 22- Single-binary deploy via `git push server master`
 23
 24## Quickstart
 25
 26```sh
 27cp samplefiles/env.sample .env
 28# edit .env to set ANALYTICS_PASSWORD and BASE_URL
 29make
 30```
 31
 32`make` (alias `make run`) installs frontend deps if needed, then runs Vite watch and `cargo run` concurrently on port 8000. Visit http://localhost:8000/login.
 33
 34First boot creates the "Proprium" property and starts tracking the dashboard's own usage.
 35
 36## System dependencies
 37
 38Local dev needs all of these on your `PATH`:
 39
 40| Tool | Why | Version |
 41|---|---|---|
 42| `rustc` / `cargo` | Build the axum binary | 2021 edition, current stable is fine (1.70+) |
 43| `bun` | Frontend deps + Vite + map builder | 1.x |
 44| `make` | Run the dev/build targets | any |
 45| `pkg-config` + OpenSSL headers | Linked at build time on Linux | distro packages: `pkg-config`, `libssl-dev` (Debian/Ubuntu), `openssl-dev` (Alpine) |
 46
 47Install hints:
 48
 49```sh
 50# Rust toolchain (recommended via rustup)
 51curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
 52
 53# Bun
 54curl -fsSL https://bun.sh/install | bash
 55
 56# System libs (Debian/Ubuntu)
 57sudo apt install -y build-essential pkg-config libssl-dev
 58
 59# System libs (Alpine)
 60sudo apk add musl-dev pkgconfig openssl-dev
 61```
 62
 63The Docker build (see `Dockerfile`) reproduces this on `rust:alpine` + `alpine:3.23`. If you only care about Docker, you do not need any of the above on the host.
 64
 65Two more files are downloaded into `data/` at runtime, no setup required:
 66
 67- `data/db.mmdb`: DB-IP City Lite (CC-BY-4.0). Refreshed if older than 30 days.
 68- `data/regexes.yaml`: canonical ua-parser regexes (Apache-2.0).
 69
 70If either download fails the server still boots: UA parsing falls back to a substring heuristic and GeoIP enrichment is skipped.
 71
 72## Configuration
 73
 74All config comes from `.env` (loaded via `dotenvy`). The full set:
 75
 76| Variable | Required | Purpose |
 77|---|---|---|
 78| `ANALYTICS_PASSWORD` | yes | Single operator password |
 79| `BASE_URL` | yes for prod | Used in absolute URLs (sitemap, og tags, embed snippet) |
 80| `PORT` | no (default `8000`) | HTTP listen port |
 81| `ANALYTICS_COOKIE_SECRET` | no | 32+ bytes for signing the session cookie. Falls back to a SHA-512 of the password, so rotating the password invalidates sessions |
 82| `ANALYTICS_DATA_DIR` | no (default `./data`) | Where the SQLite db, mmdb, and regexes live. Production sets this to `/data` |
 83| `ANALYTICS_ROOT` | no | Override the project root (where `templates/`, `dist/`, `migrations/`, `static_maps/` are read from) |
 84
 85## Make targets
 86
 87| Target | What it does |
 88|---|---|
 89| `make run` (default) | Vite watch + `cargo run` on port 8000 |
 90| `make build` | Vite assets + topojson maps + release binary (`target/release/analytics`) |
 91| `make start` | Run the release binary (after `make build`) |
 92| `make maps` | Rebuild per-country topojson under `static_maps/` from Natural Earth |
 93| `make seed` | Create or refresh a "Seed Test" property with realistic fake events. Override with `SESSIONS=2000 DAYS=60` |
 94| `make migrate FROM=<path-to-django.sqlite3>` | One-shot import of an existing Django analytics database, preserving property UUIDs so embedded snippets keep working. Add `FORCE=1` to wipe first |
 95| `make pull` | rsync the production db + geoip from `git remote server` into `data/` |
 96| `make push` | `git push` to every configured remote |
 97| `make clean` | `cargo clean` plus removing `dist/`, `node_modules/`, the SQLite db, and the mmdb |
 98
 99There are no tests or linters configured.
100
101## Deploy
102
103Production runs on Docker. The standard flow is `git push server master` to a remote whose post-receive hook runs `docker compose up --build --detach`. Sample files in `samplefiles/`:
104
105- `Caddyfile.sample`: reverse proxy with TLS
106- `env.sample`: the same `.env` shown above
107- `post-receive.sample`: the git hook
108
109Data persists to `/srv/data/analytics/` on the host (mounted into the container at `/data`).
110
111## Stack
112
113- **Backend:** axum 0.8, sqlx 0.8 against SQLite (WAL, `synchronous=NORMAL`, `busy_timeout=5s`), tower-cookies for signed sessions
114- **Templates:** minijinja 2 with a Jinja2-faithful HTML formatter
115- **Frontend:** Vite 6, Bootstrap 5 SCSS, Chart.js, d3-geo + topojson, monaspace argon font (self-hosted via `@fontsource`)
116- **Enrichment:** maxminddb (GeoIP), uaparser (UA)
117- **PDF:** embedded Typst (`typst` + `typst-pdf` + `typst-kit`), no chromium subprocess