Product page: keithah.com/products/starwatch
Starwatch is an offline-first Starlink observatory for OpenWrt and GL.iNet routers. A static Go daemon reads the dish's local gRPC API, combines dish and router-side WAN telemetry, keeps tiered history, evaluates alerts, exposes a token-authenticated REST/WebSocket API, and serves a responsive embedded dashboard. No cloud account or Internet connection is required. After installation, monitoring and administration remain entirely local; the one-line installer itself downloads packages from GitHub Pages.
The dashboard is available directly on port 9633, from Services → Starwatch in LuCI, or from Applications → Starwatch in the GL.iNet panel. The admin-panel packages pass the generated token to the dashboard through a small authenticated RPC bridge, so the router login remains the access boundary.
The public 0.1.4 feed currently supports aarch64_cortex-a53 routers. Connect
over SSH as root and run:
wget -qO- https://keithah.github.io/openwrt-packages/install-starwatch.sh | shThe installer checks the opkg architecture before changing anything. GL.iNet
SDK4 routers receive starwatchd plus gl-app-starwatch; other supported
OpenWrt routers receive starwatchd plus luci-app-starwatch. It migrates the
legacy starwatch/wattline entries to one shared keithah entry in
/etc/opkg/customfeeds.conf, preserving every unrelated feed and all existing
Starwatch configuration. It never forces a downgrade or reinstall. The shared
feed index is signed with Keith's OpenWrt publisher key installed
alongside OpenWrt's existing opkg keys; global signature checking stays enabled.
Upgrading starwatchd to 0.1.4 also performs this migration automatically, so
routers already using the legacy feed move to the dedicated feed without
rerunning the installer.
After installation, open http://<router-address>:9633, Services →
Starwatch in LuCI, or Applications → Starwatch in the GL.iNet panel.
Direct access prompts for the token shown by
uci get starwatch.main.token; the LuCI and GL.iNet launchers supply it
through their authenticated admin sessions.
The dashboard's Telemetry graphs and header metrics come only from the Starlink terminal's gRPC data. When no terminal is reachable, every current-data card is replaced by an explicit Starlink disconnected state; no attached WAN's router counters or probes are shown as substitute telemetry. Settings and the historical Events audit view remain available, and cards return automatically when the dish reconnects. The icon rail groups available cards into focused sections, while Overview visibility and compact density stay private to the current browser.
The packages use the same no-SDK pipeline verified by Wattline on a GL-X3000: a plain static Go cross-compile and hand-rolled OpenWrt packages.
make -C package all
ls -la package/out/*.ipkThe build produces:
starwatchd_0.1.4_aarch64_cortex-a53.ipk— static daemon, embedded SPA, UCI configuration, guarded dish route, token generator, and procd service.luci-app-starwatch_0.1.4_all.ipk— LuCI menu, one-method rpcd bridge, and iframe launcher.gl-app-starwatch_0.1.4_all.ipk— GL.iNet oui menu, Lua RPC bridge, and evaluated Vue 2 iframe view.
The outer .ipk is a gzipped ustar tar, not an ar archive. Its three
members are debian-binary, control.tar.gz, and data.tar.gz. This exact
format matters: the GL-X3000 opkg build segfaults on the ar form and rejects
macOS pax headers. All payload paths remain below ustar's 100-character limit.
ARCH defaults to aarch64_cortex-a53. Override the version or filename
architecture when needed:
make -C package VERSION=0.1.4 ARCH=aarch64_cortex-a53 allThe committed GL view at
package/gl-app-starwatch/www/views/gl-sdk4-ui-starwatch.common.js was produced
as the returning Vue 2 IIFE consumed by GL's eval(res.data) loader, following
the live-verified Wattline bundle contract. package/Makefile creates the
.js.gz artifact installed under /www/views; no npm or frontend build step is
required.
When iterating, remember that opkg install skips a same-version reinstall.
Use opkg install --force-reinstall, or preferably bump VERSION, so the
control metadata, filename, and feed index advance together.
OpenWrt dropbear often lacks scp support, so pipe each package over SSH:
for f in package/out/*.ipk; do
ssh root@192.168.8.1 "cat > /tmp/$(basename "$f")" < "$f"
done
ssh root@192.168.8.1 'opkg update && opkg install \
/tmp/starwatchd_0.1.4_aarch64_cortex-a53.ipk \
/tmp/luci-app-starwatch_0.1.4_all.ipk \
/tmp/gl-app-starwatch_0.1.4_all.ipk'Install either admin-panel package or both. starwatchd's post-install script
runs /etc/uci-defaults/99-starwatch, enables the service, and restarts it
so upgrades immediately use the new binary. The defaults script generates a
token when empty. It adds network.starwatch_dish, a /32 route through the
logical wan interface, only when neither UCI nor the live kernel table already
contains a dish host route. Behind a Starlink router it includes the WAN
gateway only when that gateway is on the physical WAN subnet, so a
Speedify/VPN default route cannot capture dish management traffic.
Bypass/direct-DHCP setups without a gateway use a link-scope route. The daemon
reasserts this exact /32 at startup and during discovery retries; set
manage_dish_route to 0 if another service owns the route.
GL.iNet 4.x ships the LuCI libraries without a theme. Install Bootstrap before
the LuCI launcher with opkg install luci-theme-bootstrap. The GL admin-panel
menu is loaded at login, so log out and back in once after installing
gl-app-starwatch for Applications → Starwatch to appear.
The installer registers the public GitHub Pages feed. Upgrade through normal opkg version ordering with the package selected for your router type:
opkg update
opkg upgrade starwatchd luci-app-starwatch
# GL.iNet SDK4: opkg upgrade starwatchd gl-app-starwatchMaintainers can stage the exact static GitHub Pages feed artifact locally:
make -C package feed-artifact
# unsigned local staging: package/out/pages/{Packages,Packages.gz,install-starwatch.sh,*.ipk}To sign a local feed when an OpenWrt usign private key is available, pass it
explicitly; leaving SIGN_KEY unset remains a deliberate no-op:
make -C package SIGN_KEY=/secure/path/feed.sec feed-artifact
# package/out/pages/Packages.sig is now includedUnsigned local indexes are development artifacts, not an authenticity check. Install from the HTTPS production feed with its pinned public key, or verify a release asset checksum from GitHub before installing it directly.
Version tags publish immutable GitHub releases containing only Starwatch's
three IPKs. The installer remains source-addressable at the matching tag for
the dedicated feed publisher. The old
https://keithah.github.io/openwrt-starwatch/ feed is frozen as a signed,
static migration bridge for routers upgrading to 0.1.3. New installs and all
future package updates use https://keithah.github.io/openwrt-packages/;
Starwatch no longer owns an active feed-publishing workflow.
The GL.iNet Plug-ins page can use the same feed. mwan3 is optional; Starwatch reports its status and offers an explicit failover-assist flow when installed.
/etc/config/starwatch is an opkg conffile, so local changes survive upgrades.
The safe subset is also editable from the dashboard.
config starwatch 'main'
# The default all-address bind relies on OpenWrt firewall policy to limit
# dashboard/API access to trusted LANs.
option listen '0.0.0.0'
option port '9633'
option token ''
option dish_addr '192.168.100.1:9200'
option manage_dish_route '1'
option poll_status '1'
option poll_map '900'
option wan_iface ''
option probe_hosts '1.1.1.1 8.8.8.8'
option probe_interval '2'
option location_enabled '0'
config history
option ram_hours '1'
option minute_days '7'
option quarter_days '30'
option db_path '/etc/starwatch/history.db'
option flush_secs '300'
config alerts
option webhook_url ''
option ntfy_url ''
The shipped file documents every alert enable and threshold. The principal thresholds are outage hold (30 seconds), unreachable hold (60 seconds), path loss (20%), path RTT (300 ms), path clear hold (300 seconds), and daily obstruction (2%). Changes to listen address, port, token, dish address, RAM capacity, database path, or flush cadence require a service restart.
Starwatch treats abrupt router power loss as routine. SQLite uses WAL mode with full synchronous commits and a five-second busy timeout; corrupt-database recovery remains enabled. UCI updates sync the replacement file and its parent directory before reporting success. Alert firing/resolve and failover state is stored in SQLite, so a daemon restart neither repeats an unchanged firing nor forgets the later resolve. Webhook and ntfy delivery use independent bounded workers, preventing one slow endpoint from blocking the other or the alert evaluation loop.
Telemetry serialization converts non-finite protobuf floats to safe JSON
values, preserving the REST and WebSocket streams during degraded links. A
dish speed-test status call has a two-second RPC deadline; transient
Unavailable responses are retried, while only Unimplemented marks the RPC
unsupported. MWAN polling retains its last-good snapshot across transient ubus
failures, and failover applies are serialized through mwan3 reload.
Useful service commands:
/etc/init.d/starwatch restart
/etc/init.d/starwatch reload
logread -e starwatchdAll /api/* routes require Authorization: Bearer <token>. Only /api/ws
also accepts ?token= because browser WebSocket APIs cannot set that header.
The SPA can receive a token in its page URL for bootstrap, removes it from the
URL, and uses Bearer authentication for REST calls. Read the generated token
with uci get starwatch.main.token.
| Endpoint | Purpose |
|---|---|
GET /api/status |
Dish, topology, availability, config readback, and router card |
GET /api/diagnostics?span= |
Derived latency, ping, outage, power, and configured-battery summaries |
GET /api/router |
Topology-B Starlink-router read model, clients, radios, and interfaces |
GET /api/history?series=&span= |
RAM/minute/quarter telemetry history |
GET /api/wan |
Interface probes, rates, and optional mwan3 state |
GET /api/outages?span= |
Merged dish, reachability, and path outage timeline |
GET /api/events?span= |
Alert, control, configuration, and lifecycle audit log |
GET /api/obstruction-map |
Cached obstruction grid or PNG |
POST /api/control/<action> |
Audited dish controls |
GET, POST /api/speedtest |
Speed-test state and trigger |
GET, PUT /api/config |
Read or update safe daemon settings |
PATCH /api/router/clients/{mac} |
Confirmed client rename and Starwatch-owned schedule block/unblock |
PATCH /api/router/wifi |
Guarded scalar Wi-Fi/radio edits and credential-preserving BSS edits |
GET /api/ws |
One-hertz snapshots and asynchronous events |
The complete wire format and operational constraints are in
API.md and STARWATCH-SPEC.md. Release notes
are maintained in CHANGELOG.md.
Please report vulnerabilities privately — see SECURITY.md. The
opkg feed is usign-signed; the installer pins the public key (fingerprint
f6c72c675c844b91, package/starwatch-feed.pub)
and leaves signature checking enabled.
Starwatch is licensed under the GNU Affero General Public License v3.0 or
later (AGPL-3.0-or-later); see LICENSE. Vendored protobufs under
router/third_party/ retain their own upstream license.

