A status page that health-checks registered websites and automatically locks down a domain in Cloudflare when it starts failing. Port of Status Guard onto Hono + Inertia.js + Vue3 on Cloudflare Workers.
- Checks every registered website on a schedule and records the result
- Shows a 90-day uptime history per site at
/ - When a domain keeps failing, enables its
auto:-prefixed Cloudflare WAF custom rules; disables them again once the domain recovers
Stack
- ORM: Drizzle ORM (
postgres-jsdriver) - Database: Postgres. In production this is Neon accessed through Cloudflare Hyperdrive — Hyperdrive hides the actual provider, so the app code just talks to plain Postgres
- Scheduling: Cloudflare Cron Triggers (the Worker's
scheduledhandler — no external scheduler like trigger.dev) - Buffering: check results stay in a Durable Object and reach the database every 15 minutes
bun install
docker compose up -d
cp .env.example .env
bun run db:migrate
bun run devwrangler.jsonc's hyperdrive[0].localConnectionString points at the Postgres container from docker-compose.yml, so bun run dev gets a working database out of the box — both the SSR page and the scheduled handler can be exercised locally.
There is no admin UI; rows are inserted directly into the database.
| Table | Purpose |
|---|---|
website |
A monitored site. domain is the primary key, zone_id is the Cloudflare Zone ID whose WAF rules get toggled. |
api |
Optional extra endpoints to check for the same domain (method/header/body). |
status / trigger |
Written automatically by the scheduled handler; not edited by hand. |
To auto-lock a zone when a site is down, create a Cloudflare custom rule whose description starts with auto: — the health check will flip its enabled flag on failure and back off on recovery.
This is the token the deployed Worker itself uses to edit WAF custom rulesets — separate from the deploy-time token used by CI (see below). Issue a Zone-scoped token that can edit WAF custom rulesets, then set it:
cp .dev.vars.example .dev.vars # local dev
wrangler secret put CLOUDFLARE_API_TOKEN # production, one-time — not managed by CI- Create a Postgres database on Neon and copy its connection string.
- Create the Hyperdrive config and put the returned ID into
wrangler.jsonc'shyperdrive[0].id:wrangler hyperdrive create <name> --connection-string="postgres://..."
- Add the connection string as the
DATABASE_URLGitHub Actions secret (see below) so CI can migrate it, or run it manually:DATABASE_URL="postgres://..." bun run db:migrate
.github/workflows/ci.yml runs on every push and pull request:
ci— typecheck, lint, format check, build. Runs for all pushes and PRs.deploy— runs only on push tomain, aftercipasses: applies pending migrations toDATABASE_URL, thenbun run deploy(build +wrangler deploy).
Configure these repository secrets for the deploy job to work:
| Secret | Used for |
|---|---|
DATABASE_URL |
Direct Neon connection string, for drizzle-kit migrate |
CLOUDFLARE_API_TOKEN |
Deploy auth for the wrangler CLI (needs Workers Scripts edit permission) — distinct from the runtime token above |
CLOUDFLARE_ACCOUNT_ID |
Account the Worker is deployed to |
The runtime CLOUDFLARE_API_TOKEN Worker secret (previous section) is set once via wrangler secret put and is intentionally not managed by CI.
bun run dev # local dev (Vite + Cloudflare Workers runtime)
bun run build # production build
bun run deploy # build + wrangler deploy (normally run by the `deploy` CI job, not by hand)
bun run cf-typegen # regenerate CloudflareBindings types from wrangler.jsonc
bun run db:generate # generate a migration from schema changes
bun run db:migrate # apply migrations to DATABASE_URL
bun run lint / format / typecheckSee CONTRIBUTING.md for development workflow and coding standards.