Rain link hub
React (Vite) front end, a cookieless first-party tracker (Flask + Postgres), and a Compose stack with HTTPS, nightly backups and uptime monitoring.
Edit your links
Everything lives in links.json (handle, taglines, featured link, social links). The front end, the tracker's allowlist and the link checker all read this one file.
Also set the real URL in index.html and add a 1200x630 public/og.png for link previews.
Develop
npm install
npm run dev # tracking is off in dev
Deploy
cp .env.example .envand fill it in (letters and digits only in passwords).- Point your domain's DNS at the server. Caddy gets the HTTPS certificate automatically.
- Put the SSH key for the backup machine at
backup-ssh/id_ed25519. docker compose up -d --build
Note: the DB roles are created only on the first start of an empty volume. If you change the passwords later, change them in Postgres too.
What gets tracked
Visits, clicks per link, referrer host, country, device. No cookies, no stored IPs. A visitor is a hash of IP + user agent with a key that changes daily, so uniques are per day. Tracking is skipped when the browser sends Do Not Track.
Country comes from the CF-IPCountry header if you sit behind Cloudflare, otherwise from a MaxMind GeoLite2-Country.mmdb in ./geoip, otherwise it shows XX.
Bots (user agent filter), cross-site requests and unknown link ids are dropped silently. Limits per IP: visits 20/min, clicks 40/min.
Reading the data from another project (WireGuard)
Set PG_BIND to this server's WireGuard address (for example 10.8.0.1:5432). Connect as stats_ro (read-only, even if you try to write) and query the views:
stats_daily, stats_link_clicks, stats_referrers, stats_countries, stats_devices, plus the raw visits, clicks, link_health tables.
Backups and monitoring
backuprunspg_dumpat 03:00 and copies it toBACKUP_TARGETwith rsync. Prune old dumps on that machine (for example a cronfind ... -mtime +30 -delete). Restore withpg_restore -d rainhub file.dump.- Uptime Kuma is on
KUMA_BIND(default localhost:3001). Add monitors forhttps://your-domain/andhttps://your-domain/api/health. Optionally add two push monitors and paste their URLs in.env: one reports backup success or failure, one reports dead links. linkcheckchecks every link daily and writes the result tolink_health. X, Discord and similar sites often block bots, so a 403 counts as alive.
Later
Kubernetes manifests, once the Compose setup is running the way you like.