- hypr/apps.lua - hypr/autostart.lua - hypr/envs.lua - hypr/hyprland.lua - hypr/hyprsunset.conf - hypr/input.lua - hypr/looknfeel.lua - hypr/omasettings.lua - hypr/xdph.conf - omarchy/branding/about.txt - omarchy/branding/screensaver.txt - omarchy/extensions/omarchy-menu.jsonc - omarchy/hooks/battery-low.d/play-warning-sound.sample - omarchy/hooks/font-set.d/show-font-notification.sample - omarchy/hooks/post-boot.d/weather.sample - omarchy/hooks/post-update.d/install-voxtype.hook - omarchy/hooks/post-update.d/setup-agent.hook - omarchy/hooks/post-update.d/setup-fingerprint.hook - omarchy/hooks/post-update.d/show-update-notification.sample - omarchy/hooks/pre-refresh-pacman.d/add-custom-repo.sample - omarchy/hooks/theme-set.d/show-theme-notification.sample - omarchy/shell.json - omarchy/shell.toml - omarchy/theme.name - omarchy/themes/azure-glow/README.md - omarchy/themes/azure-glow/alacritty.toml - omarchy/themes/azure-glow/btop.theme - omarchy/themes/azure-glow/hyprland.conf - omarchy/themes/azure-glow/hyprlock.conf - omarchy/themes/azure-glow/icons.theme - … 269 more
242 lines
12 KiB
Markdown
242 lines
12 KiB
Markdown
# Nexthop
|
||
|
||
**Is it your Wi-Fi, or your ISP? Know before you reboot anything.**
|
||
|
||
Nexthop sits in your [Omarchy](https://omarchy.org) bar and watches both
|
||
halves of your connection — this machine to the router, and the router to
|
||
the internet. When things feel slow, one glance tells you which side of
|
||
the router owns the problem.
|
||
|
||

|
||
|
||
## Why you'd want it
|
||
|
||
- **An answer, not a graph.** One 0–100 score in the bar, colour-coded.
|
||
Green means stop worrying. When it drops, the panel says why — in the
|
||
words you'd use to a person: responsiveness, reliability, speed.
|
||
- **"Is it me or is it them", settled.** The Overview draws your laptop,
|
||
your router and the internet with a live latency number on each leg.
|
||
The slow leg is the guilty one.
|
||
- **Proof your ISP can't wave away.** Every outage is logged with its
|
||
start, duration and which side failed. *Copy report* produces the
|
||
plain-text summary a support desk actually asks for.
|
||
- **It sees what you can't.** A router silently kicking your laptop off
|
||
Wi-Fi, an access point renegotiating to a crawl, the 3 a.m. outage that
|
||
was over before breakfast — all in the log, with timestamps and
|
||
durations.
|
||
- **Speed answers without speed tests.** Small hourly checks keep the
|
||
speed score honest; the big saturating test runs only when you ask.
|
||
|
||
## It won't get in your way
|
||
|
||
- **No root, ever.** No sudo, no capabilities, no packet capture. It runs
|
||
as your user and reads what any process may read.
|
||
- **You won't feel it.** About 3 % of one CPU core and ~30 MB of memory —
|
||
no fan, no stutter, nothing competing with your work.
|
||
- **It doesn't clog your line.** The probes are pings and payload-free
|
||
handshakes and add up to a few MB an hour; the hourly speed check is
|
||
up to about 20 MB on a fast line and a fraction of that on a slower one,
|
||
and can be turned off. Nothing saturates your connection
|
||
unless you press the button.
|
||
- **Private by construction.** Everything it measures stays on your machine
|
||
— no account, no cloud, no telemetry. Even your own IP is shown masked, so
|
||
a screenshot of the panel is safe to post. The one thing it asks the
|
||
outside world is whether a newer version exists: once a day it asks the
|
||
repository you installed from, sends nothing about you, and shows a small
|
||
marker if so. Turn that off in the panel's Setup tab (the cog) and it
|
||
asks nothing.
|
||
|
||
## Made for Omarchy
|
||
|
||
Nexthop is not a ported app — it is built for this desktop. It follows
|
||
your theme automatically, uses the shell's own type and spacing, and
|
||
opens instantly from the bar like every other panel. It is MIT-licensed,
|
||
built in the open, and shaped by community feedback — the masked WAN
|
||
address on the Overview came from a reader's suggestion the day after
|
||
launch.
|
||
|
||
## Install
|
||
|
||
```bash
|
||
omarchy plugin add https://github.com/x3me/omarchy-nexthop.git --enable
|
||
```
|
||
|
||
Updating is Omarchy's own `omarchy plugin update`, which shows you
|
||
what changed before applying it. Nexthop checks once a day whether a newer
|
||
version is published and marks the panel header if so; it never installs
|
||
anything itself.
|
||
|
||
Requirements: `python3`, `ping`, `curl`, `ss`, `ip` and `git` (all present on
|
||
a stock Omarchy; `git` only serves the daily update check), `iw` for Wi-Fi
|
||
detail, `wl-copy` for Copy report, optionally `nmcli` for the metered flag
|
||
and `speedtest` (Ookla) for peak tests.
|
||
|
||
| Latency, by leg | Speed |
|
||
|---|---|
|
||
|  |  |
|
||
|
||
| Wi-Fi | Applications |
|
||
|---|---|
|
||
|  |  |
|
||
|
||
The event log, naming who did what — including the router that kicks:
|
||
|
||

|
||
|
||
## Using it
|
||
|
||
- **Bar widget**: the index, the lag figure, or just the icon
|
||
(`displayMode` on the widget's entry in `~/.config/omarchy/shell.json`).
|
||
Colour is the verdict; during an outage it shows how long you've been
|
||
down. Click opens the panel, middle-click runs a peak test.
|
||
- **Panel tabs**: Overview (is it me or is it them), Latency (window picker,
|
||
per-leg stats, latency under load), Speed (content history + last peak),
|
||
Wi-Fi (the local leg in detail, airtime, link events), Apps (who is using
|
||
the connection), Events (what happened + Copy report), and a Setup cog
|
||
with the four settings worth reaching from the panel. Arrow keys or 1–6
|
||
switch tabs.
|
||
- **CLI**: `bin/nexthop live | query --window 24h | events | tests | report | peak`
|
||
— all JSON except `report`.
|
||
- **IPC**: `omarchy-shell io.github.x3me.nexthop toggle | speedTest |
|
||
showTab Latency`
|
||
|
||
## What it measures
|
||
|
||
- **Two-leg latency, twice a second.** One persistent probe to your
|
||
gateway, others to the internet. The difference between the legs is
|
||
your ISP; the gateway leg is your Wi-Fi.
|
||
- **Lag** — one number for how the connection feels, folding latency, jitter
|
||
(RFC 3550 IPDV) and packet loss, reported as best / typical / worst.
|
||
- **An experience index (0–100)** from three components — the weakest sets
|
||
the number and the other two nudge it, so one broken dimension cannot
|
||
hide behind two good ones:
|
||
- *Responsiveness* — scored from lag
|
||
- *Reliability* — uptime, charged in time: outages in full, brief
|
||
self-healed interruptions at half
|
||
- *Speed* — scored from small periodic **content checks** (up to ~20 MB on
|
||
a fast line, ~4 MB on a slow one,
|
||
hourly by default), not from saturating speed tests. No configuration:
|
||
the score answers "is it fast enough" on an experience-anchored curve
|
||
(diminishing returns past ~100 Mbps), minus a penalty when the line
|
||
drops well below **its own recent p90** — so shared-office variance
|
||
stays quiet while genuine degradation shows. Setting a plan in the
|
||
widget settings switches to plan-accountability scoring instead
|
||
- **Peak speed tests, on demand only.** Prefers the official Ookla
|
||
`speedtest` CLI when installed; falls back to Cloudflare, then fast.com —
|
||
both need nothing beyond `curl`. Loaded latency (bufferbloat) is captured
|
||
during every run by the probes that were already watching.
|
||
- **Wi-Fi health**: signal against a labelled scale, band/channel/rates,
|
||
and the airtime counters (retries, failures, beacon loss) that explain
|
||
why Wi-Fi feels slow when the signal bar looks full.
|
||
- **Link events**: every change of access point says who ended the
|
||
previous association — a *roam* (this machine chose to move), a *kick*
|
||
(the access point deauthenticated us, with its 802.11 reason code) or a
|
||
*drop* (the link fell over) — plus channel and signal deltas,
|
||
associations, and sustained Wi-Fi rate drops, logged with durations.
|
||
- **WAN address**: the IP this connection appears from, on the Overview
|
||
path — masked (`103.87.…`) with tap-to-reveal, because panel screenshots
|
||
end up on forums. Asked of `speed.cloudflare.com`, a host the daemon
|
||
already talks to; shown live, never written to history.
|
||
- **Per-application traffic** — top apps by TCP connection counters
|
||
(`ss -tinp`, no root, no packet capture), each with a one-minute history
|
||
strip and session totals. What Linux won't attribute without privileges
|
||
(QUIC/UDP, overhead) is shown as its own bucket rather than hidden.
|
||
- **Outage detection** with a notification once an outage has lasted a few
|
||
seconds and one when it clears — naming the leg that failed. Brief
|
||
self-healed interruptions are logged, never notified. A wan outage needs the
|
||
probes to agree: if TCP handshakes keep succeeding while pings go
|
||
unanswered, the log records an "ICMP went quiet" event instead — no
|
||
alarm for downtime you are not having.
|
||
- **History**: per-minute for 7 days (configurable), hourly for a year,
|
||
every test and event kept. A month of monitoring stays under ~12 MB.
|
||
- **Copy report** — a plain-text summary of the window you are looking at,
|
||
with timestamps, both legs and loss. The thing an ISP actually asks for.
|
||
|
||
## How it works
|
||
|
||
The QML plugin is a thin reader. All measurement lives in **nexthopd**, a
|
||
Python 3 daemon (standard library only, no pip), spawned and supervised by
|
||
the plugin's shell service. Five files are the whole contract — four the
|
||
daemon writes, one the panel writes for it:
|
||
|
||
| file | cadence | consumer |
|
||
|---|---|---|
|
||
| `$XDG_RUNTIME_DIR/nexthop/live.json` | 2× per second | the bar widget |
|
||
| `$XDG_RUNTIME_DIR/nexthop/recent.json` | every 5 s | the panel's 30-min graphs |
|
||
| `$XDG_RUNTIME_DIR/nexthop/apps.json` | every 3 s | the Apps tab |
|
||
| `~/.local/state/nexthop/history.db` | 1-min rows | `nexthop query`, longer windows |
|
||
| `~/.local/state/nexthop/config.json` | when a setting changes | the daemon — **derived, not edited**: the panel rewrites it from your bar entry every time the shell starts, so changes made here are overwritten. Settings live in `~/.config/omarchy/shell.json` |
|
||
|
||
The three snapshots are rewritten many times a minute between them and mean
|
||
nothing after a reboot, so they live in the session's runtime directory,
|
||
which is a tmpfs: they never touch the disk. History and settings stay
|
||
under `~/.local/state`. The daemon never talks to the shell, so either side
|
||
restarts without the other noticing — and your history survives every
|
||
theme change.
|
||
|
||
To keep monitoring while the shell is down, install the optional
|
||
systemd unit (see the comments in [`nexthopd.service`](nexthopd.service),
|
||
which also confines the daemon with the systemd sandboxing it can live inside);
|
||
the daemon holds a lock, so the shell service simply attaches.
|
||
|
||
### What it talks to, and what that costs
|
||
|
||
The internet leg is measured by a small pool of instruments: ICMP and a
|
||
TCP handshake to the anchor you configure (`1.1.1.1` by default), plus TCP
|
||
handshakes to `speed.cloudflare.com` and `dns.google` — one probe target
|
||
outside Cloudflare, so a Cloudflare incident cannot silence the whole
|
||
pool. The **two best** instruments — fewest losses, steadiest tails,
|
||
re-ranked every five minutes with flap damping — feed the score; the rest
|
||
idle at a tenth of the rate. One anchor having a bad day stops being your
|
||
connection's bad day. Beyond the pool, the only other contacts are the
|
||
speed-test hosts named at the end and the once-a-day update check against
|
||
the repository you installed from.
|
||
|
||
| Probe | Where | Cost |
|
||
| --- | --- | --- |
|
||
| ICMP | your gateway, and the anchor while seated | ~30 MB/day per target on the wire at the default 500 ms — two 84-byte packets a second; the gateway's share never leaves your LAN |
|
||
| TCP handshakes | the instrument pool, port 443, ~1/s while seated | ~20 MB/day per seated instrument — connections opened and closed, no payload |
|
||
| Reachability check | speed.cloudflare.com/cdn-cgi/trace | one ~1 KB HTTPS request when the network changes and hourly after that; every 30 s only while a sign-in page is suspected. Proves the real internet answered, and supplies the WAN address |
|
||
| Content check | speed.cloudflare.com | sized to the line — up to ~20 MB on a fast one, ~4 MB on a slow one; hourly, and can be turned off |
|
||
| Peak test | Ookla / Cloudflare / fast.com | up to ~600 MB, **only ever when you ask** |
|
||
| Update check | the repository you installed from (`git ls-remote`) | one request a day, carrying nothing about you; off in the Setup tab |
|
||
|
||
The TCP probes exist because ICMP is not what applications
|
||
experience: routers commonly answer pings from hardware while real traffic
|
||
waits in the queues that actually cause delay, and they rate-limit pings
|
||
under load. Measuring both, against the same host, shows the difference
|
||
rather than assuming it.
|
||
|
||
Peak tests size themselves to saturate the line for ~10 s each way — far
|
||
less on a slow one — and run only from the panel, by middle-clicking the
|
||
bar widget, or via IPC.
|
||
|
||
**Privileges: none.** No sudo, no capabilities, no packet capture. The
|
||
daemon runs as your user; everything it reads is world-readable (`/sys`
|
||
counters, `ping`, `iw`, `ss`).
|
||
|
||
## Remove
|
||
|
||
```bash
|
||
omarchy plugin remove io.github.x3me.nexthop
|
||
```
|
||
|
||
Measurement history stays in `~/.local/state/nexthop/`; delete that
|
||
directory too if you want nothing left behind. If you installed the
|
||
optional systemd unit: `systemctl --user disable --now nexthopd` and
|
||
remove `~/.config/systemd/user/nexthopd.service`.
|
||
|
||
## Development
|
||
|
||
```bash
|
||
python3 -m unittest discover -s test # fixtures recorded from real hardware
|
||
python3 -m nexthopd # run the daemon in the foreground
|
||
```
|
||
|
||
Design mockups live in `design/` as the source artboards of the project's
|
||
design canvas.
|
||
|
||
## License
|
||
|
||
MIT © [Extreme Labs](https://github.com/x3me)
|