Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

kanri docs

kanri is a vim-modal system powertool for Linux — network, process, memory, disk, and hardware, watched and controlled from one TUI and one scriptable CLI.

Source lives at git.kanri.management/jaghack/kanri.

Install

kanri is a single static-ish binary, no runtime beyond what’s listed per method below. Pick your OS.

Status: the AUR package and apt repository below are packaging templates in this repo (packaging/aur/, packaging/debian/) — not live yet, pending the first tagged release. Building from source (Nix flake or plain cargo) works today.

Arch Linux

yay -S kanri

(Coming soon — not yet published to the AUR. Until then, build from source below, or use the PKGBUILD directly:)

git clone https://git.kanri.management/jaghack/kanri.git
cd kanri/packaging/aur
makepkg -si

Ubuntu / Debian

curl -fsSL https://kanri.management/apt/key.gpg | sudo gpg --dearmor -o /usr/share/keyrings/kanri.gpg
echo "deb [signed-by=/usr/share/keyrings/kanri.gpg] https://kanri.management/apt stable main" | sudo tee /etc/apt/sources.list.d/kanri.list
sudo apt update && sudo apt install kanri

(Coming soon — the apt repository isn’t hosted yet. Build a .deb locally in the meantime: see packaging/debian/README.md in the repo.)

NixOS (flake)

{
  inputs.kanri.url = "git+https://git.kanri.management/jaghack/kanri.git";

  outputs = { self, nixpkgs, kanri, ... }: {
    nixosConfigurations.yourhost = nixpkgs.lib.nixosSystem {
      # ...
      modules = [
        {
          environment.systemPackages = [ kanri.packages.x86_64-linux.default ];
        }
      ];
    };
  };
}

Or try it without installing anything:

nix run git+https://git.kanri.management/jaghack/kanri.git

Build from source (any distro, works today)

Needs a Rust toolchain (edition 2024, so a reasonably recent one) and libclang (one dependency’s build script needs it for bindgen).

git clone https://git.kanri.management/jaghack/kanri.git
cd kanri
cargo build --release
./target/release/kanri

Or with Nix, no system Rust install needed:

nix develop   # or: nix-shell
cargo run

After installing

kanri works with zero config. Some features need one-time, deliberate setup you do yourself (kanri never grants privilege on its own) — see wireguard.md for WireGuard status/control and capture.md for packet capture. Everything else (network devices, processes, memory, disk, hardware, Tailscale) works out of the box.

See config.md for configuration and keybindings.md for the full keybinding reference.

Keybindings

All of kanri’s TUI input is vim-modal: Normal mode for navigation and actions, Command mode (:) for typed commands, Search mode (/) for live filtering. Every chord below is fully remappable — see config.md.

Several chords are contextual: the same key does different things depending which tab (and sub-view, for Network) is active. This is deliberate — dd always means “delete/kill/forget the selected thing,” just applied to whatever “the selected thing” is on the current screen, rather than one bespoke keybind per subsystem.

Global

KeyAction
h j k lMove left / down / up / right
0Move to the start of the current line/list
ggJump to the top
GJump to the bottom
gtNext tab
gTPrevious tab
{n}gtJump to tab number n (1-indexed)
:Open the command line (same grammar as the CLI — see cli.md)
/Open the search/filter line (contextual — see below)
?Toggle the help overlay (auto-generated from your live keymap)
qQuit — or close the help overlay first, if it’s open
Ctrl+CQuit unconditionally, regardless of mode

Contextual

KeyMeaning
ddDelete the selected thing — always goes through a y/N confirm first. Kills the selected process (Process tab), forgets the selected saved connection (Network → Devices).
u“Power up” the selected thing. Activates a saved connection (Network → Devices), brings a WireGuard interface up (Network → WireGuard).
x“Power down” the selected thing. Deactivates a connection (Network → Devices), brings a WireGuard interface down (Network → WireGuard).
[ / ]Cycle the Network tab’s sub-view: Devices → Diagnostics → Sockets → WireGuard → Tailscale → Bandwidth → Scan → Capture.
SpaceContext-dependent “do the thing”: run a subnet scan (Scan), start/stop a live capture (Capture), toggle Tailscale up/down (Tailscale), collapse/expand the selected drive (Disk).
cOpen the WiFi-connect prompt — SSID, then a password (Network → Devices only; Enter with a blank password connects to an open network).
oToggle showing small system/boot/recovery partitions alongside each drive’s main ones (Disk tab).
/Live filter for whichever list is on screen: process command/user/PID, sockets, subnet scan results, per-process bandwidth, WireGuard peers, Tailscale peers, or live captured packets — each field it makes sense to match against, case-insensitive. Empty query clears the filter.

Search filter fields, per view

ViewMatches against
ProcessCommand, username, PID
Network → SocketsProtocol, state, local/peer address, process name, PID
Network → ScanIP, hostname
Network → BandwidthProcess name
Network → WireGuardInterface, endpoint, allowed IPs
Network → TailscaleHostname, any Tailscale IP, OS
Network → CaptureSource, destination, protocol, dissected info

Remapping

~/.config/kanri/config.toml:

[keymap.normal]
"j" = "move_down"
"dd" = "delete_selected"

Only the chords you mention change — everything else keeps its default. See config.md for the full list of action names you can bind.

Configuration

kanri works with zero config — every field below has a sensible compiled-in default. Config lives at ~/.config/kanri/config.toml (respects $XDG_CONFIG_HOME), or pass --config <path> to use a different file. A missing config file is not an error.

Full example

# Show a Japanese companion label under every tab/section label.
# Off by default — a terminal powertool needs every column of width.
jp_labels = false

# How often live-ticking views (Overview, Process, Memory, Disk,
# Hardware) refresh, in milliseconds.
refresh_interval_ms = 1000

[theme]
# Overrides the default theme source (~/.config/quickshell/theme.json).
# Point this at any JSON file with the same shape to use kanri outside
# that theming pipeline. Every field in that file is optional — kanri
# falls back to a compiled-in dark palette for anything missing.
override_path = "/path/to/theme.json"

[thresholds]
# Color-coding cutoffs (0-100) shared by every gauge/table that shows a
# percentage-like value — CPU%, memory/disk usage, sensor temperatures.
warn_at = 70.0
crit_at = 90.0

[keymap.normal]
# Chord -> action name. Only the chords listed here change; every other
# chord keeps its compiled-in default. See "Action names" below for the
# full list of what you can bind. See keybindings.md for what each one
# does and which are contextual per-tab.
"j" = "move_down"
"k" = "move_up"
"dd" = "delete_selected"

theme

kanri reads the same live theme file a matugen-based rice pipeline generates, and hot-reloads it on change — no restart needed. Every field in that JSON is optional; kanri falls back to a built-in dark palette (Catppuccin-Mocha-adjacent) for anything absent, including when no theme file exists at all. Recognized keys (all optional): base, surface, overlay, textPrimary, textSecondary, textMuted, accentNetwork, accentSystem, success, warning, error — each a "#rrggbb" hex string.

thresholds

Applies everywhere a value is shown as a percentage-like gauge or colored number: below warn_at is “success” colored, between warn_at and crit_at is “warning,” at or above crit_at is “error.”

keymap.normal

An overlay, not a replacement — set only the chords you want to change. A chord can be any sequence of keys kanri’s input layer recognizes (a single character like "j", or a short sequence like "gg"). An unknown action name is logged and skipped at startup, not a fatal error.

Action names

The right-hand side of a keymap.normal entry — every name below is a valid target:

move_down       move_up         move_left        move_right
move_line_start jump_top        jump_bottom
next_tab        prev_tab
enter_command   enter_search    show_help        quit
delete_selected up_selected     down_selected
next_sub_view   prev_sub_view   trigger
connect_wifi    toggle_extra

See keybindings.md for what each one does, including the ones that are contextual per-tab (e.g. trigger starts a subnet scan on the Scan sub-view, but toggles Tailscale on the Tailscale sub-view).

CLI reference

Run kanri with no arguments for the TUI. Any subcommand runs it in CLI mode instead: prints once, exits, scriptable. --json works on every subcommand and switches to machine-readable output.

Every subcommand here is the exact same code path the TUI uses — there is one implementation per action, not a separate CLI and TUI version of each. The TUI’s : command line even parses with this same grammar, so anything you can run from a shell you can also run without leaving the TUI.

kanri [OPTIONS] [COMMAND]

Options:
      --json             Emit machine-readable JSON instead of human-readable text
      --config <CONFIG>  Path to an alternate config.toml (defaults to the XDG config dir)

net — network devices and connections (NetworkManager)

CommandDoes
net listList devices and saved connections
net show <device>Full detail for one device, by interface name
net up <name>Activate a saved connection profile
net down <name>Deactivate a saved connection profile, if active
net disconnect <device>Disconnect whatever’s active on a device
net forget <name>Permanently delete a saved connection profile
net connect-wifi <ssid> [--password <pw>]Create and activate a new WiFi connection
net wg-up <interface>Bring a WireGuard interface up via wg-quick — needs a matching sudo rule for that exact interface name (see wireguard.md)
net wg-down <interface>Bring a WireGuard interface down via wg-quick

proc — processes and CPU usage

CommandDoes
proc list [--sort cpu|mem|pid|name] [--filter <text>]List running processes (default sort: cpu)
proc kill <pid> [--signal term|kill]Send a signal (default: term)
proc renice <pid> <value>Change nice value (-20 highest priority, 19 lowest)

ts — Tailscale status and control

CommandDoes
ts statusThis device + tailnet peers
ts upConnect to the tailnet — no elevated privilege needed on a typical setup
ts downDisconnect. Doesn’t touch your normal internet route — Tailscale is an overlay network, not your default route
ts exit-node [node]Set the active exit node by hostname or IP; omit node to clear it. Routes all of this device’s traffic through the chosen node — treat this one with real caution, unlike a plain up/down toggle

One-shot system info

CommandDoes
overviewCPU, memory, network throughput, disks — one snapshot
memMemory and swap usage
diskDisk usage by mount point
hwHost/CPU/kernel info and live sensor temperatures

--json shape

Every command’s --json output is the same data the human-readable text is built from — nothing is dropped or summarized differently between the two. Field names are plain snake_case, matching the Rust struct they come from. Pipe through jq as usual; kanri handles a closed pipe (kanri disk --json | jq '.[0]') the same way df/free do, not with a broken-pipe panic.

What’s TUI-only, for now

net show’s sub-views beyond the device/connection list — Diagnostics, Sockets, subnet Scan, per-process Bandwidth, WireGuard status (write control via wg-up/wg-down is scriptable, reading status isn’t yet), and live packet Capture — don’t have CLI equivalents yet. They’re real, working features, just reachable from the TUI only so far.

WireGuard

kanri talks to WireGuard through wg/wg-quick directly — not a reimplementation, the real tools. Reading status (wg show) and bringing an interface up/down (wg-quick up/down) both need root, since wg-quick creates network interfaces and can touch routing tables. kanri never has standing root itself; it shells out through sudo -n (non-interactive — fails fast and cleanly if it isn’t authorized, rather than hanging on a password prompt) for each call.

That means you configure the sudo access, scoped as narrowly as you want it. kanri ships no default grant.

Read-only status

# /etc/sudoers.d/kanri-wireguard
your_username ALL=(root) NOPASSWD: /path/to/wg show all dump

Resolve the real path first — command -v wg often returns a symlink (e.g. /run/current-system/sw/bin/wg on NixOS), and some sudo configurations match the literal command string rather than resolving symlinks themselves:

readlink -f "$(command -v wg)"

Use that path in the sudoers rule.

Up/down control

Same idea, one rule per interface name you actually want kanri to control — resist the temptation to wildcard this:

your_username ALL=(root) NOPASSWD: /path/to/wg-quick up wg0
your_username ALL=(root) NOPASSWD: /path/to/wg-quick down wg0

A wildcard (wg-quick up *) technically works, but it means anything that can run as your user can bring up or tear down any WireGuard config on the box, not just ones you meant to expose to kanri. Scoping per-interface costs one extra line per tunnel and closes that off entirely.

wg show’s own private-key exposure

wg show all dump includes each interface’s private key in cleartext in its output. If this machine has other, less-trusted local accounts, scope the sudo rule to trusted users only — don’t hand out blanket wg access to every account on a shared box.

NixOS example

security.sudo.extraRules = [
  {
    users = [ "your_username" ];
    commands = [
      { command = "${pkgs.wireguard-tools}/bin/wg show all dump"; options = [ "NOPASSWD" ]; }
      { command = "${pkgs.wireguard-tools}/bin/wg-quick up wg0"; options = [ "NOPASSWD" ]; }
      { command = "${pkgs.wireguard-tools}/bin/wg-quick down wg0"; options = [ "NOPASSWD" ]; }
    ];
  }
];

${pkgs.wireguard-tools}/bin/wg is already the real, fully-resolved store path — no readlink -f dance needed on NixOS specifically.

Nothing configured?

kanri’s WireGuard sub-view (and net wg-up/net wg-down) still work without any of this — wg show just reports no interfaces, and sudo -n wg-quick up ... fails with a clear “a password is required” style error instead of hanging.

Packet capture

The Capture sub-view (Network tab) shells out to tshark for real protocol dissection — not raw frames, actual decoded source/destination/ protocol/info columns, the same as opening Wireshark itself. Capturing packets needs CAP_NET_RAW/CAP_NET_ADMIN, which a normal user doesn’t have by default. kanri doesn’t grant this itself; you configure it once, the same way you would for Wireshark/tcpdump on any system.

The standard way

Most distros already have a mechanism for this via Wireshark’s own packaging:

# Debian/Ubuntu
sudo dpkg-reconfigure wireshark-common   # answer "yes" to the non-superuser prompt
sudo usermod -aG wireshark $USER

# Arch
sudo setcap cap_net_raw,cap_net_admin=eip $(readlink -f $(command -v dumpcap))
sudo usermod -aG wireshark $USER          # if you'd rather scope it to a group

Log out and back in (or reboot) afterward — group membership is fixed for your whole session at login, not per-process. A new terminal window inside an already-running desktop session does not pick up a newly-added group; only a fresh login does.

NixOS example

programs.wireshark = {
  enable = true;
  # dumpcap.enable defaults to true: wraps dumpcap with the capability,
  # restricted to the `wireshark` group (not world-executable).
};
users.users.your_username.extraGroups = [ "wireshark" ];

Why kanri doesn’t just grant this itself

Packet capture can read cleartext credentials off the wire for anyone sharing the same network segment — materially more sensitive than, say, per-process bandwidth counts. That’s worth a deliberate, visible choice on your part, not something a TUI silently arranges the first time you press a key.

Nothing configured?

The Capture sub-view still works without any of this — tshark fails fast (well under a second) with a clear permission error instead of hanging, and kanri shows the whole error, not just its first line.