Repository navigation
Releases: willowhawk-k/NetLights
Release list
NetLights 1.9.6
The rest of the slowdown fix, plus a correct picture of bridged virtual machines. Soaked for
eight days across six builds before the cut; the final one ran three and a half days clean.
The traffic LED no longer piles up animations. 1.9.5 removed the picker leak, and five
days of production uptime confirmed that part stayed fixed — yet the main thread was back at a
full core, this time inside SwiftUI's animation evaluation. Each interface LED blinked with a
repeatForever animation that was restarted every time traffic resumed on that interface. Such
an animation never finishes, so SwiftUI kept every one of them (7,681 after five days) and
evaluated the whole list on every frame. The LED's pulse is now computed from the clock on
each update, with no SwiftUI animation behind it at all: idle LEDs cost nothing and a pulsing
one never accumulates anything. Same look as before.
The port query no longer walks the whole I/O Registry every three seconds. Reading the
registry tree is how NetLights maps USB and Thunderbolt devices to ports. It used to serialize
every property of every registry node on every third poll, and a single pass had grown to
about two seconds of kernel time on an M-series MacBook Pro — most of a core, continuously,
for data that only changes when something is plugged in. The walk now runs when IOKit reports
that a USB device, Thunderbolt switch, disk, or HID device appeared or went away, when you
press Refresh, or once a minute as a fallback, and it skips the handful of driver-hosted
HID and display nodes whose property reads were 90 % of the cost and that nothing reads.
The topology shown is unchanged; what changes is that an idle NetLights idles.
Less work per refresh on the main thread. With a virtual machine bridged, the layout engine
was re-deriving the band geometry and the Virtual band's rows dozens of times per tick and
re-hashing every interface, including a string-splitting parse of Thunderbolt port names, on
every geometry lookup. Those are now computed once per layout and cached; on an M-series
MacBook Pro the main thread's busy share with a bridged VM dropped from 88 % to 47 %.
A bridged virtual machine's bridge now sits under the port it bridges. With a UTM virtual
machine in bridged mode, bridge100 floated in the Data Link band with no wire to en7, the USB
Ethernet it bridges, while ifconfig bridge100 plainly listed both members. The graph had been
inferring membership from MAC addresses, which holds for the Thunderbolt bridge0 — it adopts
en1's address — and for nothing else; UTM's and Internet Sharing's bridges get a
locally-administered MAC of their own. NetLights now asks the bridge driver the way ifconfig
does (unprivileged, and it works in the sandboxed App Store build), and the Linux collector
reads /sys/class/net/<bridge>/brif. Each bridge in --dump-json gains an optional
bridgeMembers; the schema stays 3. A bridge is centred over its physical members and wired
to every visible one, including the VM side (vmenet0), which "Hide inactive" now keeps on
screen whenever its bridge is. Two bridges whose members sit close together keep the Physical
row's minimum spacing instead of overlapping, and the terminal graph, which has no wires, names
the members on the bridge's row. The VM-side port of such a bridge (vmenet0) is listed
under Virtual Adapters as a Virtual Machine port instead of under System.
Install: brew install --cask willowhawk-k/tap/netlights (or brew upgrade --cask netlights), the notarized zip below, or the Mac App Store once the review lands. Requires macOS 13 or later.
NetLights 1.9.5
NetLights 1.9.5
A single-fix patch: the app slowed down the longer it stayed open.
After a day open, clicks lagged and the process sat at 100–150 % CPU. On macOS 26,
SwiftUI's segmented Picker leaks its tag bookkeeping — a projection plus one Observation
registrar per segment — on every re-render, then re-tracks the whole accumulated set on
every layout pass. The view switcher sat inline in the main body, so it re-rendered on every
0.75 s refresh: one leaked set per tick. A nine-hour instance held 65,000 leaked projections
and 126,000 registrars, with the main thread ~60 % busy servicing them. The picker now lives
in its own view that re-renders only when the tab changes. A fixed build held steady at 3
projections after 120 refreshes, where the old one gained ~140 over the same interval and
ran at a third of the CPU. Independently reported for macOS 26.6.2 the same week.
macOS only. The Linux port is unchanged by this release and remains unreleased.
Install
brew tap willowhawk-k/tap
brew install --cask netlights
Or download NetLights-1.9.5.zip below and drag it into Applications. Signed and notarized
with an Apple Developer ID. Also on the Mac App Store.
NetLights 1.9.4
NetLights 1.9.4
A correctness release: NetLights was naming the wrong uplink.
The primary egress is now the interface that actually wins. With Wi-Fi and Ethernet both
up and both holding a default route, the status line picked whichever default route the
kernel happened to list first — which carries no priority — so a docked laptop could be told
its traffic left over Wi-Fi while it was really going out Ethernet. The Routes tab, which
ranks correctly, sat next to it disagreeing. Egress now uses that same ranking: the macOS
network-service order, or the Linux route metric.
Only the winning default route is starred. Every surface marked isDefault, which is
true for every default route — Wi-Fi, Ethernet, the VPN tunnel and any bridge each have
one — so the Routes table appeared to claim four primaries at once. Exactly one row is
marked now.
Link state is now read from the OS, not inferred from interface flags. A Wi-Fi
interface with the radio switched off keeps both IFF_UP and IFF_RUNNING set — it reads
flags=8863, byte for byte the same as a live Ethernet port — while ifconfig reports
status: inactive. NetLights inferred "up" from those flags and so counted switched-off
Wi-Fi, cable-less Thunderbolt ports and idle bridges as up. It now reads
SystemConfiguration's authoritative link state, which agrees with ifconfig on every
interface that reports one; interfaces with no link state (tunnels, loopback) keep the old
inference. Expect the "interfaces up" count to drop, and to be right.
And the primary is now visible where you'd look for it. The app's status bar names the
egress interface and its network, as the terminal dashboard's header always has; the
Interfaces table stars it in the app, the TUI and the browser; and in the graph its chip
takes an orange border and a soft glow.
Install
brew tap willowhawk-k/tap
brew install --cask netlights
Or download NetLights-1.9.4.zip below and drag it into Applications. Signed and notarized
with an Apple Developer ID. Also on the Mac App Store.
Linux: build from source — see docs/BUILDING.md.
NetLights 1.9.3
NetLights 1.9.3
The biggest release since the Linux port began: the browser and terminal UIs reach parity
with the app, and the Linux collectors are functionally complete.
Privacy and Hide inactive now work in serve. Both are resolved server-side — forced
for the graph, which is server-rendered SVG, and the right call for privacy anyway, since
serve has no authentication and masking before the response is written keeps real
addresses off the wire entirely. Toggle them in the top right or with p / h; the
state lives in the URL, so a masked view can be shared and stays masked.
The served graph draws everything the app draws. The Wi-Fi, Displays, Battery and
Bluetooth entities were positioned by the layout engine but never rendered, so wires ran to
empty space and an interface appeared to have no parent. Node sizes, labels, port brackets
and hover text now match the app, and long device names are truncated to their box instead
of overrunning their neighbours.
The browser tables are formatted by the app's own code. They previously reimplemented
six formatters in JavaScript, all of which had drifted — USB devices showed no speed, the
bus column said "USB" instead of "USB 3.2", and link-local routes were filed under
"unencrypted split-tunnel".
The terminal UI gained a Hardware band, so a USB adapter and the interface it provides
finally appear together, along with a Port column, per-width column layout, and distinct
up/down glyphs that survive --no-color.
Linux: the collector set is complete. USB device tree, external displays read from raw
EDID (including the model name, which the sandboxed Mac build cannot see), system power,
Wi-Fi SSID and link rate, Thunderbolt, route metrics, and Bluetooth. Bluetooth required
implementing the D-Bus wire protocol from scratch — BlueZ is reachable only over D-Bus, and
linking libdbus would have forfeited the single static binary the distribution plan depends
on. See docs/LINUX.md.
Routes gained a Priority column on every surface, showing the Linux route metric or the
macOS network-service order — whichever the platform has.
Fixes found by an adversarial pre-release review (19 confirmed findings, all fixed):
terminal-escape injection from a hostile USB or Bluetooth device name in the default TUI
view; two ways a malformed D-Bus reply could hang or trap the process; several paths where
privacy mode still emitted a real address (the VPN concentrator IP in SVG hover text, device
serials and Bluetooth addresses in the JSON, the live resolver inside a DNS scope label);
IPv6 resolvers silently dropped from resolvectl output when the line wrapped; and a
non-UTF-8 request byte stalling serve for its full socket timeout.
Shared fix, so it lands on macOS too: the Virtual band sized its canvas assuming an even
two-row split, so a large tunnel group compressed the tiles below their own width and they
overlapped. Widening the window used to work around it; the served graph could not.
Install
brew tap willowhawk-k/tap
brew install --cask netlights
Or download NetLights-1.9.3.zip below and drag it into Applications. Signed and notarized
with an Apple Developer ID. Also on the Mac App Store.
Linux: build from source — see docs/BUILDING.md.
There is no packaged Linux release yet.
NetLights 1.9.2
NetLights 1.9.2
A correctness release on top of 1.9.0's command-line modes. 1.9.0 and 1.9.1 were never
published — this supersedes both.
Routes are identified uniquely again
A routing table can legitimately hold several routes that match in destination, gateway and
interface — two default routes that differ only by metric, or a pair of host routes to a VPN
concentrator. 1.9.1 identified routes by exactly those three fields, so such routes shared an
identity and the Routes table could drop or mis-draw them. Fixed, and snapshots remain
reproducible: netlights --dump-json now returns byte-identical output for an unchanged
machine, which is what makes two snapshots worth comparing.
Everything 1.9.0 brought — the command line, on every OS
netlights tui— a live, full-screen terminal dashboard in the spirit oftop. Switch
views with graph / routes / interfaces / devices / dns (or1–5);
h hides inactive, p masks addresses, s re-sorts routes,SPACEpauses, q
quits. Works over SSH;tui --onceprints a single frame for pipes, cron and CI.netlights serve— the web UI on any OS: the same live graph plus/snapshot.json,
with configurable--portand--bind. Listens on127.0.0.1by default — it has no
authentication and publishes your interfaces, addresses, routes and DNS servers, so reaching
it from the network is an explicit--bind allthat warns. It also refuses unrecognised
Hostheaders, so a hostile web page can't read it out of your browser. See PRIVACY.md.
Install
brew tap willowhawk-k/tap
brew install --cask netlightsAlready have NetLights from the Mac App Store? brew install netlights-cli adds just the
command (tui and --dump-json; serve needs this build — the sandbox forbids listening).
NetLights is free and open source (MIT). The GitHub build carries the Sponsor link; the
Mac App Store build omits it per guideline 3.1.1.
NetLights 1.9.0
NetLights 1.9.0
NetLights now has a command line — the same on macOS and Linux.
netlights tui — a live terminal dashboard
A full-screen view in the spirit of top, refreshing about once a second. Switch views with
graph / routes / interfaces / devices / dns (or 1–5); h hides
inactive interfaces, p masks addresses, s re-sorts routes, SPACE pauses, q quits.
It works over SSH, adapts to your terminal's width and colour support, and tui --once prints
a single frame for pipes, cron jobs and CI.
netlights serve — the web UI, anywhere
The browser view that shipped on Linux now works on macOS too: the same live graph plus
/snapshot.json, with a configurable --port and --bind.
It listens on 127.0.0.1 by default. It has no authentication and publishes your
interfaces, addresses, routes and DNS servers, so reaching it from the network is an explicit
--bind all that prints a warning. It also refuses requests with an unrecognised Host, so a
hostile web page can't read it out of your browser. See PRIVACY.md.
Install the command
brew tap willowhawk-k/tap
brew install --cask netlightsAlready have NetLights from the Mac App Store? brew install netlights-cli adds just the
command (tui and --dump-json; the sandbox means serve needs this build).
Also fixed
- The web UI reported throughput eight times too slow — it derived rates in bytes/sec while
the app uses bits/sec. Rate derivation now lives in one shared place. --bindnever worked on Linux: the server hardcoded "all interfaces" and ignored the address
it was given. It now honours the setting, and defaults to loopback.- A crash when launched from a terminal. macOS attributes privacy requests to the responsible
process, so reading Bluetooth from a shell-launched NetLights was killed by the system. Every
command-line path now avoids that; typingnetlightshands the window off to LaunchServices
so it opens as a normal app with the full device list. - The terminal dashboard's status line was never visible (one line terminator too many).
- Device names, Wi-Fi network names and DNS search domains are supplied by other parties, so
they're now neutered before being drawn and escaped before reaching the browser.serve
gained connection timeouts and snapshot caching.
NetLights is free and open source (MIT). On the GitHub build the Sponsor link is on the
repo; the Mac App Store build omits it per App Store guideline 3.1.1.
NetLights 1.8.1
NetLights 1.8.1
A small polish release — two fixes to how the graph shows devices and link speeds.
Accurate multi-gig link speeds
A 2.5 Gigabit link now reads 2.5 Gbps instead of being truncated to "2 Gbps". Link-speed
labels show a decimal only when the standard rate needs one (2.5 / 5 Gbps) and stay whole
otherwise — 1 Gbps, 100 Mbps, 10 Gbps.
No more overlapping device chips
When one USB hub on a port has a single device and a neighbouring hub has several, the lone
device no longer drifts sideways into its neighbour. The gentle curve applied to single-file
device chains is now confined to genuine lone chains.
NetLights is free and open source (MIT). On the GitHub build, the Sponsor link is on the
repo; the Mac App Store build omits it per App Store guideline 3.1.1.
NetLights 1.8.0
NetLights 1.8.0 — See your VPN, end to end
Encrypted path, drawn end to end. When a VPN is active, NetLights traces the whole encrypted path as a distinct glowing tunnel — from the tunnel interface, through the physical carrier it actually egresses (per-tunnel, not always en0), across the Internet, out to a far-side concentrator node beyond the Internet. Hover any part for the tunnel, carrier, and server.
Split tunneling made visible. Traffic that bypasses the VPN — the public "excludes" that egress directly — is drawn as a separate unencrypted (amber) strand running alongside the encrypted pipe up the same carrier, then branching at the Internet to a "Direct" node. Encrypted-blue vs. plaintext-amber, side by side.
Routes tab, grouped. Routes now group into Direct (split-tunnel), Encrypted (VPN tunnel), and Local sections — with a one-click toggle back to a flat, numerically-sorted list.
Reveal your public IP (opt-in). A new Public IP button looks up how the internet sees you — both the exit IP (through the VPN) and the underlay IP (your carrier's real address, bypassing the tunnel) — via an on-demand STUN lookup. It's the app's only outbound network activity, and never automatic. See PRIVACY.md.
Cleaner map. Idle Thunderbolt / L2 bridge interfaces and disconnected VPN tunnels that kept a stale address no longer clutter "Hide inactive" unless they're actually carrying traffic; stacked device → port → gateway chains curve gently instead of piling up; and VM/app virtual adapters moved to the Virtual (L2+) band.
Under the hood, the network model, transforms, and graph-layout engine were lifted into a portable, Foundation-only core — groundwork for future platforms, with the macOS app behaving identically.
Free and open source (MIT). Also on the Mac App Store.
Install: download NetLights-1.8.0.zip below, unzip, and drag NetLights.app into Applications. Signed and notarized with an Apple Developer ID, so it opens with no Gatekeeper warning.
NetLights 1.7.1
NetLights 1.7.1
External storage & Target Disk Mode
External drives now appear as storage chips on the Thunderbolt/USB port they're
plugged into — capacity, medium (SSD/HDD), and interconnect on hover. A Mac connected
in Target Disk Mode is shown as a Mac chip with its disk nested beneath it, so a
decommissioning target Mac and its drive are visible right on the port (great for
knowing which port that old Mac is hanging off before you wipe it).
All read in-process from IOKit device metadata (name, size, interconnect) — the same
sandbox-safe posture as the existing USB/Thunderbolt probes. NetLights never touches the
mounted volume's files, so there's no new permission and no removable-volume prompt.
Also
- Attach/detach of ports and devices now reflects within ~3 seconds (was ~5).
NetLights is free and open source (MIT). On the GitHub build, the Sponsor link is
on the repo; the Mac App Store build omits it per App Store guideline 3.1.1.
NetLights 1.7.0
NetLights 1.7.0
Two new features and a layout fix.
DNS resolvers tab
A new DNS tab surfaces the resolvers name resolution actually uses — as diagnosable
as knowing which interface you egress on. A banner shows the active resolver set (the
"which DNS wins" answer, with the interface it rides) above a table of every network
service's set: bound interface, server addresses, search domains, and split-DNS
scoping. The OS primary service is starred, so a VPN pushing its own resolvers is
plainly visible winning over your physical uplinks'. Read live from SystemConfiguration
(SCDynamicStore) — in-process, no privileges. Privacy mode masks resolver IPs and
redacts search / scoped domains and user-named services.
In-app feedback → GitHub issue
Help ▸ Send Feedback / Report an Issue… and a link in About open a prefilled
GitHub issue tagged with your app version, macOS version, and Mac model — for bug
reports, ideas, and contributing your Mac's port layout to the per-model table.
Layout fix
- No more bracket overlap between an iPhone and a Thunderbolt port. A hardware port
now reserves layout width for its anchored interfaces' spread, not just its device
tree — so an iPhone's USB-tether channels no longer overflow their slot and collide
brackets with the neighbouring TB port.
NetLights is free and open source (MIT). On the GitHub build, the Sponsor link is
on the repo; the Mac App Store build omits it per App Store guideline 3.1.1.