-
Notifications
You must be signed in to change notification settings - Fork 0
feat(tools): add linux-diag/ dispatcher for speedtest-cli, iperf3, mtr, traceroute #13
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,115 @@ | ||
| # linux-diag — Network diagnostics dispatcher | ||
|
|
||
| This directory hosts `lzt-netdiag`, a thin bash dispatcher over four network | ||
| diagnostic tools that are not packaged for Debian Forky upstream | ||
| (`speedtest-cli`, `iperf3`, `mtr`, `traceroute`) — instead, they run inside an | ||
| Arch Linux **distrobox** called `arch-loust` and are exported back to the host | ||
| PATH via `distrobox-export --bin`. | ||
|
|
||
| ## Why a dispatcher instead of calling the wrappers directly? | ||
|
|
||
| | Concern | Without dispatcher | With dispatcher | | ||
| |---|---|---| | ||
| | Memory | `distrobox enter arch-loust -- speedtest-cli --simple` | `lzt-netdiag st --simple` | | ||
| | Tab-completion | 4 distinct command names | 1 canonical name with 6 subcommands | | ||
| | Auto-detection of local NICs | Manual `iw dev`, `ip route` | `lzt-netdiag host` | | ||
| | Tool availability | Each wrapper is independent | Pre-flight check fails fast with actionable error | | ||
|
|
||
| ## Subcommands | ||
|
|
||
| | Subcommand | Pass-through to | Notes | | ||
| |---|---|---| | ||
| | `st` | `speedtest-cli` | Accepts all native flags (`--simple`, `--json`, `--list`, `--server N`, etc.) | | ||
| | `i3` | `iperf3` | Accepts all native flags (`-c`, `-u`, `-t`, `-j`, `-b`, etc.) | | ||
| | `mtr` | `mtr` | Accepts all native flags (`-c`, `-w`, `-J`, etc.) | | ||
| | `trace` | `traceroute` | Accepts all native flags | | ||
| | `host` | host sysfs + iproute2 | Local-only nic auto-detect (interfaces, default route, wireless quality, DNS resolvers, gateway ping) | | ||
| | `version` | all four binaries | One-liner with versions + container name | | ||
| | `-h`, `--help`, `help`, no args | — | Extracted from script doc block via sed | | ||
|
|
||
| ## Setup (one-time) | ||
|
|
||
| ```sh | ||
| # 1. Create the distrobox (Arch Linux base; speedtest-cli, iperf3, mtr, | ||
| # traceroute are in upstream Arch repos). | ||
| distrobox create -i archlinux:latest -n arch-loust | ||
| distrobox enter arch-loust -- sudo pacman -S speedtest-cli iperf3 mtr traceroute | ||
|
|
||
| # 2. Export each binary to the host PATH | ||
| distrobox enter arch-loust -- distrobox-export \ | ||
| --bin /usr/bin/speedtest-cli \ | ||
| --bin /usr/bin/iperf3 \ | ||
| --bin /usr/bin/mtr \ | ||
| --bin /usr/bin/traceroute \ | ||
| --export-path ~/.local/bin/ | ||
|
|
||
| # 3. Drop this directory on the host | ||
| install -m 0755 lzt-netdiag ~/.local/bin/lzt-netdiag | ||
|
Comment on lines
+46
to
+47
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Create the install destination first.
🤖 Prompt for AI Agents |
||
| ``` | ||
|
|
||
| ## Usage examples | ||
|
|
||
| ```sh | ||
| # Bandwidth + latency in compact form | ||
| lzt-netdiag st --simple | ||
|
|
||
| # Raw JSON for scripting | ||
| lzt-netdiag st --json | jq '.download.bandwidth, .ping' | ||
|
|
||
| # Bandwidth to a specific server for N seconds | ||
| lzt-netdiag i3 -c 192.168.1.64 -t 30 | ||
|
|
||
| # UDP stream at 1 Mb/s for 5 seconds | ||
| lzt-netdiag i3 -c 192.168.1.64 -t 5 -u -b 1M | ||
|
|
||
| # Path MTU / packet-loss diagnostic | ||
| lzt-netdiag mtr 192.168.1.64 -c 30 -w | ||
|
|
||
| # JSON mtr for log ingest | ||
| lzt-netdiag mtr -J 192.168.1.64 | ||
|
|
||
| # What hop is the bottleneck? | ||
| lzt-netdiag trace 1.1.1.1 | ||
|
|
||
| # Local NIC inventory + gateway reachability | ||
| lzt-netdiag host | ||
|
|
||
| # Confirm all 4 binaries are exported and which container they're in | ||
| lzt-netdiag version | ||
| ``` | ||
|
|
||
| ## Exit codes | ||
|
|
||
| | Code | Meaning | | ||
| |---|---| | ||
| | `0` | OK | | ||
| | `1` | Argument / tool / container error (with stderr message) | | ||
| | `127` | Tool missing from host PATH (re-run `distrobox-export`) | | ||
| | Other | Pass-through from underlying tool (e.g. `mtr` returns its own codes) | | ||
|
|
||
| ## Why is this in NetBoozt? | ||
|
|
||
| The NetBoozt platform targets Windows users (see `runner-strategy.md`), but the | ||
| **build & maintain** workflow runs on Debian. When validating a release that | ||
| claims e.g. "improved connection-resilience on lossy networks", we need to | ||
| reproduce typical home-network pathologies — packet loss, MTU black holes, | ||
| slow DNS, asymmetric bandwidth — without polluting the host distro with | ||
| Arch-only packages. The dispatcher pattern lets the maintainer reproduce | ||
| field issues deterministically while keeping the host clean. | ||
|
|
||
| ## Why a single-file script? | ||
|
|
||
| - Zero dependencies beyond `bash`, `distrobox`, `iproute2`, `procps`. | ||
| - Trivial to audit (160 lines). | ||
| - Single source of truth — `lzt-netdiag --help` reads the same doc-block that | ||
| this README documents, so they cannot drift. | ||
|
|
||
| ## Customization | ||
|
|
||
| The container name is configurable via `LZT_ARCH_LOUST` env var if you need | ||
| multiple coexisting arch distroboxes (e.g. `LZT_ARCH_LOUST=arch-test`). | ||
|
|
||
| ## See also | ||
|
|
||
| - `runner-strategy.md` — Why builds use `windows-latest` (VPS runner is Linux-only) | ||
| - `tools/` — Other helper scripts committed to NetBoozt | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,177 @@ | ||
| #!/usr/bin/env bash | ||
| # lzt-netdiag — Network diagnostics dispatcher. | ||
| # | ||
| # Thin layer over speedtest-cli, iperf3, mtr and traceroute exported | ||
| # from the 'arch-loust' Arch Linux distrobox. Each subcommand is a | ||
| # pass-through to the underlying tool with its full flag surface; the | ||
| # only extras are 'host' (local NIC auto-detect) and sensible bash | ||
| # helpers (exit-code-friendly, --help via sed-extract). | ||
| # | ||
| # Why a dispatcher instead of calling the wrappers directly? | ||
| # - One short command (`lzt-netdiag st --simple`) instead of writing | ||
| # `distrobox enter arch-loust -- speedtest-cli --simple` every time. | ||
| # - Tab-completion target (single canonical name → can hook bash-completion). | ||
| # - Local-only affordances that the upstream tools don't have: | ||
| # * `host` → auto-detect Wi-Fi vs Ethernet interface + default route. | ||
| # * `version` → one-liner showing versions of all 4 tools. | ||
| # | ||
| # Setup (one-time; only needed if exporting after re-install): | ||
| # distrobox create -i archlinux:latest -n arch-loust | ||
| # distrobox enter arch-loust -- sudo pacman -S speedtest-cli iperf3 mtr traceroute | ||
| # distrobox enter arch-loust -- distrobox-export \ | ||
| # --bin /usr/bin/speedtest-cli \ | ||
| # --bin /usr/bin/iperf3 \ | ||
| # --bin /usr/bin/mtr \ | ||
| # --bin /usr/bin/traceroute \ | ||
| # --export-path ~/.local/bin/ | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| # | ||
| # Usage: | ||
| # lzt-netdiag st [--simple | --json | --list | --server N] # speedtest-cli | ||
| # lzt-netdiag i3 [-c host] [-u] [-t sec] [-j] # iperf3 | ||
| # lzt-netdiag mtr [-c N] [-w] [-J] host # mtr | ||
| # lzt-netdiag trace host # traceroute | ||
| # lzt-netdiag host # local NIC info | ||
| # lzt-netdiag version # all 4 versions | ||
| # lzt-netdiag (-h | --help | help) | ||
| # | ||
| # Examples: | ||
| # lzt-netdiag st --simple | ||
| # lzt-netdiag st --json | jq '.download.bandwidth, .ping' | ||
| # lzt-netdiag i3 -c 192.168.1.64 -t 30 | ||
| # lzt-netdiag i3 -c 192.168.1.64 -t 5 -u -b 1M # UDP 1 Mb/s, 5s | ||
| # lzt-netdiag mtr 192.168.1.64 -c 30 -w # wide report | ||
| # lzt-netdiag mtr -J 192.168.1.64 # JSON report | ||
| # lzt-netdiag host | ||
| # | ||
| # Exit codes: 0=OK, 1=argument/tool/container error, | ||
| # 127=tool missing in PATH (re-export from arch-loust). | ||
| # | ||
| # Source of truth: this file is the canonical dispatcher. If you need | ||
| # to extend, add a new case below and update the usage block above. | ||
|
|
||
| set -euo pipefail | ||
|
|
||
| CONTAINER_NAME="${LZT_ARCH_LOUST:-arch-loust}" | ||
|
|
||
| die() { | ||
| echo "lzt-netdiag: $*" >&2 | ||
| exit 1 | ||
| } | ||
|
Comment on lines
+56
to
+59
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Align the missing-tool exit-code contract.
📍 Affects 2 files
🤖 Prompt for AI Agents |
||
|
|
||
| die_missing_tool() { | ||
| echo "lzt-netdiag: $*" >&2 | ||
| # 127 matches bash convention for "command not found". | ||
| exit 127 | ||
| } | ||
|
|
||
| usage() { | ||
| # Extract the doc block between the second '#' line and the first | ||
| # blank line. Robust against edits to header. | ||
| sed -n '3,/^[^#]/p' "$0" \ | ||
| | sed -n '1,/^$/p' \ | ||
| | sed 's/^# \{0,1\}//' | ||
| exit 0 | ||
| } | ||
|
|
||
| ensure_container_running() { | ||
| command -v distrobox >/dev/null || die "distrobox not found in PATH" | ||
| # Filter distrobox list output to our specific container; avoid the | ||
| # "any container is Up" shortcut which silently passes when another | ||
| # distrobox is running but ours is not. | ||
| local row | ||
| if ! row="$(distrobox list 2>/dev/null | grep -E "(^|[[:space:]])${CONTAINER_NAME}([[:space:]]|$)")"; then | ||
| die "container '${CONTAINER_NAME}' not present. Create it: distrobox create -i archlinux:latest -n ${CONTAINER_NAME}" | ||
| fi | ||
| # Status column varies between distrobox versions (1.4.x: 3rd col; | ||
| # 1.5+: 4th col with STATUS first). Use word-anchored match. | ||
| if ! echo "$row" | grep -wq "Up"; then | ||
| die "container '${CONTAINER_NAME}' is not Up. Start it: distrobox start ${CONTAINER_NAME}" | ||
| fi | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| } | ||
|
|
||
| check_tool() { | ||
| local tool="$1" | ||
| command -v "$tool" >/dev/null 2>&1 || \ | ||
| die_missing_tool "tool '${tool}' not exported on host. Re-run: distrobox enter ${CONTAINER_NAME} -- distrobox-export --bin /usr/bin/${tool} --export-path ~/.local/bin/" | ||
| } | ||
|
|
||
| # Dispatch happens in the case below; preflight (container + tool check) | ||
| # runs ONLY for subcommands that actually need the distrobox exports | ||
| # (st, i3, mtr, trace). Help, host, and version work without them. | ||
|
|
||
| cmd="${1:-help}" | ||
| shift || true | ||
|
|
||
| case "$cmd" in | ||
| st) | ||
| ensure_container_running; check_tool speedtest-cli | ||
| exec speedtest-cli "$@" | ||
| ;; | ||
| i3) | ||
| ensure_container_running; check_tool iperf3 | ||
| exec iperf3 "$@" | ||
| ;; | ||
| mtr) | ||
| ensure_container_running; check_tool mtr | ||
| exec mtr "$@" | ||
| ;; | ||
| trace) | ||
| ensure_container_running; check_tool traceroute | ||
| exec traceroute "$@" | ||
| ;; | ||
| host) | ||
| # Local-only nic auto-detect. Uses host sysfs + iproute2. | ||
| printf "=== Interfaces ===\n" | ||
| ip -br addr show up 2>/dev/null \ | ||
| || echo "(iproute2 not on host: ip -br not found)" | ||
| printf "\n=== Default route ===\n" | ||
| ip route show default 2>/dev/null \ | ||
| || echo "(no default route)" | ||
| printf "\n=== Wireless interfaces (sysfs) ===\n" | ||
| # Cannot use `local` outside a function; use a subshell + captured stdout. | ||
| wl_list="$(for iface in /sys/class/net/*; do | ||
| [ -d "$iface/wireless" ] && basename "$iface" | ||
| done)" | ||
| if [ -n "$wl_list" ]; then | ||
| # Use mapfile so every newline-separated interface is preserved | ||
| # (read -ra <<< would only consume the first line). | ||
| mapfile -t wl_arr <<< "$wl_list" | ||
| printf " %s\n" "${wl_arr[@]}" | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| # Link quality from /proc/net/wireless | ||
| if [ -r /proc/net/wireless ]; then | ||
| printf "\n=== /proc/net/wireless ===\n" | ||
| awk 'NR>2 {printf " %s\n", $0}' /proc/net/wireless | ||
| fi | ||
| else | ||
| echo " (none)" | ||
| fi | ||
| printf "\n=== DNS resolvers (/etc/resolv.conf) ===\n" | ||
| awk '/^nameserver/ {printf " %s\n", $2}' /etc/resolv.conf 2>/dev/null \ | ||
| || echo " (none / unreadable)" | ||
| printf "\n=== Gateway reachability (5 pings) ===\n" | ||
| gw="$(ip route show default 2>/dev/null | awk '/default/ {print $3; exit}')" | ||
| if [ -n "$gw" ]; then | ||
| if command -v ping >/dev/null 2>&1; then | ||
| ping -c 5 -W 2 "$gw" 2>&1 | tail -4 | ||
| else | ||
| echo " (ping not installed on host — install iputils-ping / inetutils-ping to enable)" | ||
| fi | ||
| else | ||
| echo " (no gateway detected)" | ||
| fi | ||
| ;; | ||
| version) | ||
| printf "speedtest-cli: " ; speedtest-cli --version 2>&1 | head -1 | ||
| printf "iperf3: " ; iperf3 --version 2>&1 | head -1 | ||
| printf "mtr: " ; mtr --version 2>&1 | head -1 | ||
| # traceroute writes version to stderr (modern 2.x); capture both | ||
| printf "traceroute: " ; traceroute --version 2>&1 | head -1 | ||
| printf "container: %s\n" "$CONTAINER_NAME" | ||
| ;; | ||
| -h|--help|help|"") | ||
| usage | ||
| ;; | ||
| *) | ||
| die "unknown subcommand '${cmd}'. Run: lzt-netdiag --help" | ||
| ;; | ||
| esac | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Re-include the parent directory and its contents.
Because
tools/is ignored, the negation fortools/linux-diag/does not maketools/linux-diag/lzt-netdiagorREADME.mdtrackable. Use parent/content exception rules, for example:Verify with
git check-ignore -v tools/linux-diag/lzt-netdiag tools/linux-diag/README.md.📝 Committable suggestion
🤖 Prompt for AI Agents