Skip to content

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, server https://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=3 in /etc/nuc-watchdog.env, then systemctl 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.

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 copy AstradialDevelopment/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 as nuc-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).

# on the cloud box: host timing should read p99<2ms, ticks 50, stalls 0
ssh root@147.93.168.216
LQ=$(ls -t /var/log/linkqual/*.csv | head -1)
grep ',TIMER,' "$LQ" | tail -5
grep ',NUC,'   "$LQ" | tail -5      # tunnel loss/jitter
systemctl status nuc-linkqual