Monitoring¶
Overview¶
Astradial uses three monitoring layers:
- Netdata -- Real-time system metrics, alerts, and dashboards
- Upptime -- Public status page with uptime tracking via GitHub Actions
- NUC offline alerting -- Two independent watchers that page via ntfy within ~75s when the on-premise NUC SIP gateway goes offline (see below)
Netdata¶
Installations¶
| Location | URL | Notes |
|---|---|---|
| NUC (on-premise) | http://192.168.0.13:19999 | Installed with --static-only to avoid CPU overheat during build |
| Cloud server | http://147.93.168.216:19999 | Standard installation |
Both instances are connected to Netdata Cloud under the hari-space-company space.
Monitored Metrics¶
- CPU usage and temperature
- RAM usage
- Disk usage and I/O
- Network throughput
Alerts¶
Alerts are configured in Netdata Cloud with email and mobile push notifications:
| Alert | Trigger | Severity |
|---|---|---|
| Node unreachable | Agent stops reporting | Critical |
| CPU temperature high | Sustained high temp | Warning |
| Disk full | Disk usage above threshold | Critical |
| RAM exhausted | Memory usage above threshold | Warning |
NUC Static-Only Install
The NUC uses --static-only Netdata installation. Building Netdata from source caused the NUC to overheat and crash. See Troubleshooting - Error 11.
Upptime¶
Upptime provides a public-facing status page powered by GitHub Actions. It checks service availability every 5 minutes and publishes results to a static site.
| Property | Value |
|---|---|
| Repository | astradial/upptime |
| Action used by workflows | astradial/uptime-monitor@v1.41.3-astradial (a forked, patched action — not upstream upptime/uptime-monitor) |
| Status Page | status.astradial.com |
| Config File | .upptimerc.yml |
| Deployment | GitHub Pages from gh-pages branch |
We pin a forked action, not upstream
Upstream upptime/uptime-monitor has issues disabled and is effectively unmaintained. It also crashes intermittently because of a flaky GitHub API call. We forked to astradial/uptime-monitor, patched getUptimeMonitorVersion, and pin all workflows to our fork's tag. Any change to action behaviour goes through the fork → new tag → bump uses: lines. Do not point workflows back at upptime/uptime-monitor — the crash returns. See Troubleshooting - Error 49.
Monitored Services¶
Upptime checks 5 services every 5 minutes.
GitHub Actions Workflows¶
| Workflow | Schedule | Purpose |
|---|---|---|
| Uptime CI | Every 5 minutes | Check service availability |
| Response Time CI | Every 5 minutes | Measure response times |
| Graphs CI | Every 5 minutes | Generate uptime graphs |
| Summary CI | Daily | Generate daily summary |
| Static Site CI | Daily | Build and deploy status page |
Deployment¶
The status page is built as a static site and deployed to GitHub Pages using the peaceiris/actions-gh-pages action, which pushes to the gh-pages branch.
# Deployment step in workflow
- uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./build
Why peaceiris/actions-gh-pages
Without this action, the site builds but never deploys to GitHub Pages, resulting in a 404. See Troubleshooting - Error 12.
NUC Offline Alerting¶
The on-premise NUC is the Tata PSTN gateway and sits on an unstable office WiFi link. When it drops off the network, all Tata inbound/outbound stops. This alerting fires a phone push within ~75s so the outage is acted on immediately.
Why a phone call can't be the alert¶
A PSTN call to Hari's mobile routes over the Tata trunk, which runs on the NUC. When the NUC is down, that call path is down too. The alert channel must be out-of-band: a data push (ntfy) that does not touch the NUC. The watcher must also run somewhere other than the NUC — a dead device can't alert.
Architecture — two independent watchers¶
ntfy.sh topic (in /etc/nuc-watchdog.env & HC integration)
▲ ▲
Layer 1: active │ push │ push
ping over WG ──────┘ │
[cloud 147.93.168.216] │
nuc-watchdog.service │
│ ping 10.10.10.2 q15s │
▼ Layer 2: dead-man's-switch
[ NUC 10.10.10.2 ] ──heartbeat q60s──► [ Healthchecks.io ]
nuc-heartbeat.timer
| Layer 1 — Cloud watchdog | Layer 2 — Dead-man's-switch | |
|---|---|---|
| Runs on | Cloud VPS 147.93.168.216 | NUC (pushes out) + Healthchecks.io |
| Signal | ICMP ping to NUC 10.10.10.2 over WireGuard, every 15s | NUC heartbeats Healthchecks every 60s |
| Fires DOWN | 4 consecutive failed pings (~60–75s) | No heartbeat for period+grace (~2 min) |
| Recovers | 4 sustained OK pings (hysteresis vs. WiFi flapping) | Next successful heartbeat |
| Catches | NUC/network/tunnel offline | Same plus survives a cloud-VPS failure, and catches a silently dead gateway (heartbeat only sent when Asterisk is up and WG handshake fresh) |
| Alert | ntfy urgent (loud, overrides silent) on DOWN; normal on recover; re-alert every 30 min while down | ntfy via Healthchecks integration |
The two layers detect "NUC down" over independent paths, so a failure of either the cloud VPS or the Healthchecks side still leaves one working detector. Netdata Cloud's "node unreachable" alert is a third, bonus detector.
Components¶
| Component | Location | Purpose |
|---|---|---|
nuc-watchdog.sh | cloud /usr/local/bin/ | Ping loop + state machine + ntfy push |
nuc-watchdog.service | cloud /etc/systemd/system/ | Runs the watchdog, Restart=always |
/etc/nuc-watchdog.env | cloud (mode 600) | Holds NTFY_TOPIC (secret — not in git) |
/var/lib/nuc-watchdog/state.env | cloud | Persisted state (STATE/FAILS/OKS/LAST_ALERT) so a restart mid-outage doesn't re-spam |
nuc-heartbeat.sh | NUC /usr/local/bin/ | Heartbeat to Healthchecks, gated on Asterisk + WG health |
nuc-heartbeat.timer / .service | NUC /etc/systemd/system/ | Fires the heartbeat every 60s |
/etc/nuc-heartbeat.env | NUC (mode 600) | Holds HC_URL Healthchecks ping URL (secret — not in git) |
Secrets live in the env files, not in git
The ntfy topic and the Healthchecks ping URL are bearer secrets (anyone holding them can read the alerts or post spam). They are stored only in the *.env files on each host. Subscribe to the topic in the ntfy mobile app to receive alerts. iOS: enable ntfy as a Critical Alert so urgent pushes override silent / Do-Not-Disturb.
Healthchecks.io check config¶
- Project owner:
hari@astradial.com - Check name:
NUC SIP gateway - Schedule: Simple, Period 1 minute, Grace 1 minute
- Integration: ntfy → topic from
/etc/nuc-heartbeat.env, serverhttps://ntfy.sh
Test it¶
Layer 1 — simulate a real outage from the cloud (safe: drops only ICMP, so SIP/RTP keep flowing). Auto-removes the rule on exit:
ssh root@147.93.168.216 '
trap "iptables -D OUTPUT -p icmp -d 10.10.10.2 -j DROP 2>/dev/null" EXIT
iptables -I OUTPUT -p icmp -d 10.10.10.2 -j DROP
sleep 90 # watch /var/lib/nuc-watchdog/state.env flip to DOWN, expect 🔴 push
'
# rule removed on exit → expect 🟢 recovery push ~60s later
Layer 2 — exercise the Healthchecks → ntfy path without disrupting anything:
HC=$(ssh root@nuc.astradial.com 'grep -o "https://hc-ping.com/[^ ]*" /etc/nuc-heartbeat.env')
curl -fsS "$HC/fail" # expect a DOWN push from Healthchecks
sleep 10
curl -fsS "$HC" # back to green
Tune¶
- Faster detection (~45s, slightly more flap-prone): on the cloud, set
DOWN_THRESHOLD=3in/etc/nuc-watchdog.env, thensystemctl restart nuc-watchdog. Other knobs:INTERVAL,UP_THRESHOLD,REMINDER_SECS. - Check status:
systemctl status nuc-watchdog(cloud);systemctl list-timers nuc-heartbeat.timer(NUC).
Scope¶
Ping detects network/device offline. The Layer-2 heartbeat additionally gates on Asterisk being active and the WG handshake being fresh, so it also catches a "NUC pingable but gateway dead" condition. Neither layer inspects call quality or per-trunk SIP registration — that's nuc-linkqual (below) / Netdata territory.
Link Quality & Host vCPU Monitoring (nuc-linkqual)¶
nuc-watchdog (above) catches NUC offline. nuc-linkqual catches what degrades calls while everything still pings "up": tunnel packet loss/jitter, and — since 2026-06-22 — prod host vCPU-scheduling stalls (the Error 73 noisy-neighbour failure mode that is invisible to steal=0 dashboards).
- Runs on cloud
147.93.168.216,nuc-linkqual.service(Restart=always). Script/usr/local/bin/nuc-linkqual.sh(local copyAstradialDevelopment/NUC/nuc-linkqual.sh). - Every ~60 s it logs a sample to
/var/log/linkqual/linkqual-YYYYMMDD.csv(30-day retention) and ntfy-alerts on BAD/RECOVERED transitions with hysteresis (same topic + pattern asnuc-watchdog).
| Probe | CSV row | Pages when |
|---|---|---|
| NUC tunnel | ts,NUC,10.10.10.2,loss%,avg,max,jitter | loss ≥15% or jitter ≥120 ms for ≥6 rounds |
| V7 tunnel | ts,V7,10.20.0.2,… | logged only (no page — BSNL flaps on its own; muted via NOALERT_LABELS=V7, the script default. To re-enable paging: NOALERT_LABELS="" in /etc/nuc-watchdog.env + restart) |
| Host vCPU timing | ts,TIMER,localhost,p99,max,stalls,ticks | p99 ≥ 2 ms OR asterisk ticks < 48 for ≥3 rounds |
Host-timer probe (Error 73 early warning)¶
Each round, on prod itself: (1) a load-free 2000-sample 5 ms-sleep wakeup test → p99/max lateness + count of stalls >10 ms (healthy KVM p99 <1 ms; the Jun-2026 incident was p99 29–33 ms / 5–7% stalls), and (2) worst-of-3 asterisk -rx "timing test" → ticks/sec (target 50; broken dipped to 18–46). Tunables (env): TIMER_P99_WARN=2, TIMER_TICKS_WARN=48, TIMER_BAD_THRESHOLD=3, TIMER_SAMPLES=2000; set TIMER_PROBE=0 to disable.
Why it exists / does it affect calls?
Error 73 recurred three times and each time was caught only by hospitals complaining — every graph was green because Contabo doesn't expose steal. This probe pages the on-call instead. The wakeup test only sleeps (load-free by design), so it adds ~0.3% of box CPU and does not affect call quality (measured: 0 stalls in its own readings while live calls run).