Skip to content

Aswincloud/status-page

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

5 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

🟒 aswincloud status

A self-hosted, BetterStack-style live status page for a home network.

Built on the Cloudflare edge so the page stays up even when home β€” or the prober β€” is down.

β–Ά Live demo: status.aswincloud.com

Deploy Database Prober CI Free tier Deps


How it works

A status page that lives at home dies with home β€” useless. So the page runs on Cloudflare's global edge (a Worker + a D1 SQLite database), and an external always-on server runs a tiny Docker prober that checks your home and pushes heartbeats in.

 β”Œβ”€ external always-on server ─┐         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ Cloudflare edge ───────────┐
 β”‚  prober (Docker)            β”‚         β”‚  Worker  Β·  D1  Β·  per-minute cron      β”‚
 β”‚   every 30s:                β”‚  HTTPS  β”‚                                         β”‚
 β”‚     check home  ───────────────────▢  β”‚  POST /api/ingest   β†’ record + detect   β”‚
 β”‚     POST heartbeat          β”‚ (Bearer)β”‚                       up/down flips     β”‚
 β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜         β”‚  GET  /api/status   β†’ JSON for the page β”‚
                                         β”‚  scheduled()        β†’ watchdog + prune  β”‚
   visitor ─── GET / ─────────────────▢  β”‚  static assets      β†’ the status page   β”‚
                                         β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                                        β”‚ on a down/up flip
                                                        β–Ό
                                            πŸ“§ Email Β· πŸ’¬ Slack Β· ✈ Telegram

Both failure modes surface correctly:

What breaks How it's caught
🏠 Home is down The prober's check fails β†’ pushes down β†’ recorded, incident opened, alerts fire.
πŸ–₯️ The prober/server itself dies No heartbeats arrive β†’ the Worker's per-minute cron watchdog sees stale data (>2 min) and records a synthetic outage. No silent green.

A note on the check type. The demo monitors torrent.aswincloud.com, which is proxied through Cloudflare. A ping to it would only reach Cloudflare's edge (always up) and show false-green β€” so the monitor uses an HTTP check that flows through Cloudflare to the home origin: 200 when home is up, 52x when it isn't. For a direct (non-proxied) host, ping or tcp work too. The prober supports all three.


✨ Features

  • Overall banner β€” All systems operational Β· Partial outage Β· Major outage
  • Per-monitor cards with a live status pill
  • 90-day uptime bar β€” hover any day for that day's uptime %
  • Uptime % over 24h / 7d / 30d
  • Response-time sparkline (last 2 hours, SVG, breaks the line on downtime)
  • Incident timeline β€” ongoing + resolved, with durations
  • Alerts on every down/recovery β€” Email (Resend), Slack, and Telegram, each independent
  • Dark / light theme toggle Β· fully responsive Β· zero frontend dependencies
  • Config-driven β€” add a monitor by editing one JSON file; the page auto-discovers it

Supported check types: http Β· tcp Β· ping.


πŸ—‚οΈ Layout

wrangler.jsonc     Worker + D1 + cron + static-assets + custom-domain config
schema.sql         D1 tables + starter monitor seed
src/
  index.ts         fetch() router + scheduled() cron watchdog
  api.ts           /api/ingest (auth) + /api/status (edge-cached JSON)
  db.ts            D1 helpers + Env types + tunables (STALE_MS, RETENTION_MS)
  stats.ts         uptime %, 90-day buckets, latency series, incidents
  alerts.ts        email / Slack / Telegram β€” each activates only when its secrets exist
public/            The status page β€” index.html Β· styles.css Β· app.js
prober/            The Docker prober (copied to the external server)

πŸš€ Part 1 β€” Deploy the page (Cloudflare)

Requires a Cloudflare account. For the custom domain, your zone should be on Cloudflare.

npm install

# 1. Create the D1 database, then paste the printed database_id into wrangler.jsonc
npx wrangler d1 create status-db

# 2. Create the tables (remote = the real database the Worker uses)
npx wrangler d1 execute status-db --file schema.sql --remote

# 3. Set the shared ingest secret β€” SAVE it, the prober needs the same value
#       openssl rand -hex 32
npx wrangler secret put INGEST_TOKEN

# 4. Deploy
npx wrangler deploy

Custom domain β†’ in wrangler.jsonc the routes block maps status.aswincloud.com; since the zone is on Cloudflare, DNS + TLS are provisioned automatically on deploy. Before a domain is attached, the Worker is live at home-status.<account>.workers.dev.


🐳 Part 2 β€” Run the prober (external, always-on server)

Copy the prober/ folder to that server, then:

cd prober
cp .env.example .env
nano .env                       # INGEST_TOKEN = the same value from step 3 above

# config.json already points at https://status.aswincloud.com/api/ingest
docker compose up -d
docker compose logs -f          # expect: "ok β€” home-network:140ms"

restart: unless-stopped + Docker-on-boot keep it running across reboots and crashes.


βœ… Verify end-to-end

  1. Open the page β€” the monitor turns green within ~30s; sparkline + 90-day bar fill in.
  2. curl -s https://status.aswincloud.com/api/status | jq '.overall, .monitors[0].up' β†’ "operational", true.
  3. Outage test: docker compose stop the prober. Within ~2 min the card flips red, an incident opens, alerts fire. docker compose start β†’ it recovers and the incident shows resolved with a duration.

βž• Add another monitor

Edit prober/config.json, then docker compose restart. The new card appears automatically.

{
  "monitors": [
    { "id": "home-network", "name": "Home Network", "type": "http", "target": "https://torrent.aswincloud.com" },
    { "id": "jellyfin",     "name": "Jellyfin",     "type": "tcp",  "target": "192.168.1.50:8096" },
    { "id": "router",       "name": "Router",       "type": "ping", "target": "192.168.1.1" }
  ]
}

ping/tcp to LAN addresses require the prober to have a network route to them. To delete a monitor's history immediately: npx wrangler d1 execute status-db --remote --command "DELETE FROM monitors WHERE id='jellyfin'; DELETE FROM checks WHERE monitor_id='jellyfin';"


πŸ”” Alerts

src/alerts.ts fires on every down/recovery transition. Each channel activates only when its secrets are present β€” run any combination, or none. Secrets live in Cloudflare, never in this repo.

πŸ“§ Email (Resend)

npx wrangler secret put RESEND_API_KEY   # resend.com β€” sending domain must be verified
npx wrangler secret put ALERT_FROM       # "aswincloud status <status@aswincloud.com>"
npx wrangler secret put ALERT_TO         # aswin@aswincloud.com  (comma-separated for several)

πŸ’¬ Slack β€” create an app with the chat:write scope, /invite the bot to a channel, grab the channel ID (C0…):

npx wrangler secret put SLACK_BOT_TOKEN
npx wrangler secret put SLACK_CHANNEL

✈ Telegram β€” create a bot via @BotFather, read your chat id from https://api.telegram.org/bot<TOKEN>/getUpdates:

npx wrangler secret put TELEGRAM_BOT_TOKEN
npx wrangler secret put TELEGRAM_CHAT_ID

After setting secrets, just push to main (or npx wrangler deploy) β€” no code change.


πŸ”„ Continuous deployment

Connected to Cloudflare Workers Builds: every push to main runs npx wrangler deploy; pushes to other branches run npx wrangler versions upload (preview). Worker secrets persist across builds.

The prober is not part of this pipeline β€” it's a container on an external box. Update it there with git pull && docker compose up -d --build.


πŸ§ͺ Local development

npx wrangler dev                                            # local Worker + D1 at :8787
npx wrangler d1 execute status-db --file schema.sql --local # seed the local DB
cd prober && INGEST_TOKEN=devtoken INGEST_URL=http://localhost:8787/api/ingest node prober.js
# (give wrangler dev the matching token via a .dev.vars file: INGEST_TOKEN="devtoken")

Open http://localhost:8787.


πŸ“ Notes & limits

  • Fits comfortably in Cloudflare's free tier (Workers + D1 + cron).
  • Raw checks are pruned after 90 days; uptime % and the day-bar use indexed aggregates, and /api/status is edge-cached ~15s so page loads never re-scan.
  • Tunables: STALE_MS (2 min) and RETENTION_MS (90 d) in src/db.ts; check interval in prober/config.json (intervalSeconds).
  • Secrets (INGEST_TOKEN, alert credentials) are Cloudflare Worker secrets β€” never committed.

Built with Cloudflare Workers Β· D1 Β· Docker β€” and zero frontend dependencies.

About

🟒 Self-hosted, BetterStack-style live status page for a home network β€” Cloudflare Worker + D1 + a Docker prober, with email & Slack alerts. Runs on the free tier.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors