repos
/ status-rust master

status-rust

mirror archived upstream

Single-binary self-hosted uptime monitoring and status pages on Rust axum: HTTP probes, Lighthouse audits, SEO crawler, and PDF reports.

axumdockerrustself-hostedsqlitestatus-pageuptime-monitoringvite

6.9 KB · 156 lines · markdown Raw History
  1> **Archived.** This project is no longer in use and no longer maintained. Last updated July 2026.
  2
  3# Status
  4
  5A self-hosted uptime monitor and status page. HTTP checks every 3 minutes,
  6daily Lighthouse audits, weekly in-process SEO crawls, and alerts via email
  7and Discord webhook on state transitions.
  8
  9Single-binary axum service backed by SQLite. Originally a Django service; that Django
 10codebase has since been restored in the `status` repository. The data
 11from the Django era can be migrated in via `./status migrate <django.sqlite3>`
 12(preserves Property UUIDs so existing public status URLs keep working).
 13
 14
 15## Features
 16
 17- HTTP uptime checks with rolling uptime percentages and recent-uptime bars
 18- Lighthouse audits (performance, accessibility, best practices, SEO) with
 19  weighted breakdown and top savings opportunities
 20- In-process SEO crawler (reqwest + scraper) extracting title, description,
 21  canonical, OG tags, and H1 per page, plus 38 SEO/a11y/perf/security checks
 22- Security header analysis (HTTPS, HSTS, HSTS preload, X-Frame-Options, etc.)
 23- Alert state machine with debounce on flaps (two consecutive non-200s to
 24  go down, immediate 200 to come back up)
 25- Direct-to-MX email and Discord webhook alerts on state transitions only
 26- PDF + markdown report export per property (PDF rendered in-process via
 27  embedded Typst, no chromium subprocess; markdown rendered from a template)
 28
 29
 30## Stack
 31
 32| Concern         | Crate / Tool                                          |
 33|-----------------|-------------------------------------------------------|
 34| Web framework   | axum + tokio                                          |
 35| Database        | sqlx + SQLite (WAL, `synchronous=NORMAL`)             |
 36| Auth            | tower-cookies signed sessions                         |
 37| Template engine | minijinja                                             |
 38| HTTP client     | reqwest (rustls)                                      |
 39| Crawler         | scraper + html5ever + robotstxt + hickory-resolver    |
 40| Lighthouse      | `bun run --bun node_modules/.bin/lighthouse`          |
 41| Email           | lettre (direct-to-MX, opportunistic STARTTLS)         |
 42| PDF             | embedded Typst (`typst` + `typst-pdf` + `typst-kit`)  |
 43| Static assets   | Vite + Bun, Bootstrap 5, Chart.js, monaspace font     |
 44
 45
 46## Requirements
 47
 48You need docker installed for a quick production start, or you can read the
 49`Dockerfile` for the exact dependency list and adjust for your distro.
 50
 51For local development:
 52
 53- rust (cargo) for the backend
 54- bun for everything JS: the frontend bundler (Vite) AND the `lighthouse` CLI.
 55  The rust binary invokes lighthouse via `bun run --bun node_modules/.bin/lighthouse`,
 56  which symlinks `node``bun` so the shim's `#!/usr/bin/env node` shebang
 57  resolves to bun's runtime. No nodejs/npm required.
 58- chromium for Lighthouse's own audits (PDF reports do not need chromium;
 59  they go through embedded Typst)
 60
 61
 62## Running locally
 63
 64    cp samplefiles/env.sample .env  # set STATUS_PASSWORD at minimum
 65    make run                         # vite watch + cargo run on port 8000
 66
 67Server boots, applies migrations, and starts the scheduler in-process. Open
 68<http://localhost:8000>, log in with the password from `.env`, add a property
 69URL.
 70
 71
 72## Configuration
 73
 74All config comes from `.env` (loaded via `dotenvy`):
 75
 76| Variable | Required | Purpose |
 77|---|---|---|
 78| `STATUS_PASSWORD` | yes | Single operator password |
 79| `BASE_URL` | yes for prod | Used in absolute URLs (sitemap, og tags, alert email links). No trailing slash |
 80| `PORT` | no (default `8000`) | HTTP listen port |
 81| `STATUS_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| `ALERT_EMAIL` | no | Recipient for outage / recovery emails. Leave unset to disable email |
 83| `DISCORD_WEBHOOK_URL` | no | Discord webhook for outage / recovery embeds. Leave unset to disable |
 84| `STATUS_DATA_DIR` | no (default `./data`) | Where the SQLite db lives. Production sets this to `/data` |
 85| `STATUS_ROOT` | no | Override the project root (where `templates/`, `dist/`, `migrations/` are read from) |
 86| `CHROMIUM_BIN` | no | Path to chromium for Lighthouse. Falls back to `PATH` lookup, then a `/opt/playwright-browsers/` glob |
 87
 88
 89## Make targets
 90
 91| Target | What it does |
 92|---|---|
 93| `make run` (default) | Vite watch + `cargo run` on port 8000, plus the in-process scheduler |
 94| `make build` | Vite assets + release binary (`target/release/status`) |
 95| `make start` | Run the release binary (after `make build`) |
 96| `make pull` | rsync the production sqlite db from `git remote server` into `data/` |
 97| `make migrate FROM=<path-to-django.sqlite3>` | One-shot import of an existing Django status database, preserving Property UUIDs so public status URLs keep working. Add `FORCE=1` to wipe first |
 98| `make push` | `git push` to every configured remote |
 99| `make clean` | Remove `target/`, `dist/`, `frontend/node_modules/`, root `node_modules/`, and `data/` |
100
101There are no tests or linters configured.
102
103
104## Importing an existing Django status DB
105
106If you have a SQLite from the Django version of this project, you can keep
107your existing properties + check history:
108
109    make migrate FROM=/path/to/django/db.sqlite3
110
111Add `FORCE=1` to wipe an existing local rust DB first. The migration preserves
112Property UUIDs so any public status URLs you've shared keep working.
113
114
115## Production deploy
116
117The same `git push server master` post-receive hook flow used by the rest of
118my projects:
119
120Server:
121
122    apk update && apk upgrade && apk add docker docker-compose caddy git iptables ip6tables ufw
123    ufw allow 22/tcp && ufw allow 80/tcp && ufw allow 443/tcp && ufw --force enable
124    rc-update add docker boot && service docker start
125    mkdir -p /srv/git/status.git && cd /srv/git/status.git && git init --bare
126
127Local:
128
129    git remote add server [email protected]:/srv/git/status.git
130    git push --set-upstream server master
131
132Server:
133
134    mkdir -p /srv/docker && cd /srv/docker && git clone /srv/git/status.git status && cd /srv/docker/status
135    cp samplefiles/Caddyfile.sample /etc/caddy/Caddyfile
136    cp samplefiles/env.sample .env  # edit STATUS_PASSWORD, BASE_URL, ALERT_EMAIL, DISCORD_WEBHOOK_URL
137    cp samplefiles/post-receive.sample /srv/git/status.git/hooks/post-receive && chmod +x /srv/git/status.git/hooks/post-receive
138    mkdir -p /srv/data/status && chown -R 1000:1000 /srv/data/status
139    docker-compose up --build --detach
140    rc-update add caddy boot && service caddy start
141
142
143## Backups
144
145All data is stored in `/srv/data/status/` and your repo is in
146`/srv/git/status.git/`. Back up both of those folders and you have a complete
147backup. The `Caddyfile` and `.env` are easy enough to recreate but back them
148up too if you want to be thorough.
149
150
151## Support
152
153I won't be providing user support for this project. I'm happy to accept good
154pull requests and fix bugs but I don't have time to help people run or use
155this project.