- 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)
|