Skip to content
Projects

BrettKinny/tower-dashboard has 46 GitHub stars and 0 forks. GitHot measured 0 stars in 24 hours and +1 in 7 days. Last checked 2026-11-09 22:19 UTC. Last push 2026-10-21.

How these counts are measured

TUI monitoring dashboard for Unraid: btop, nvtop, ctop, and fan-status composed in a single tmux window over SSH, launchable from any XDG app launcher.

Measured star growth

in 24h
0
in 7 days
+1
in 15 days
Update delayed
in 1 month
Update delayed

Net star changes over overlapping periods, not daily totals. Missing measurements are left blank. Stars measure attention, not software quality.

README.md

Original

Tower Dashboard icon

Tower Dashboard: a TUI for monitoring your Unraid box

A live, read-only monitoring dashboard for Unraid: btop, nvtop and ctop composed in a tmux session that runs on the box itself, with fans, CPU temp and the clock in a lazygit-style footer. The workstation just attaches to it over a single SSH connection, launchable from any XDG app launcher. One click, one keystroke, one window — everything you need to glance at to know the box is healthy.

On omarchy it's also a bar plugin: an icon that turns red when something on the box needs attention (a disabled disk, a full or hot drive, parity errors, an unhealthy container, a stalled fan), a popup with the details, and a right-click into the full dashboard.

Tower Dashboard screenshot Running in foot on omarchy with the etherial theme.

Built for Unraid. The tower-side pieces are Unraid-coupled (mdcmd, emhttp's disks.ini, /mnt/disk* / /mnt/cache / /mnt/user, RAM-FS restored from /boot/config/go). The workstation-side launcher will happily SSH into any Linux host, but the array-status and boot-restore bits assume Unraid.

Shared as a starting point, not a finished product. Fork it, rip out what doesn't match your box, rewire the rest. The generalizable bits — the tmux composition pattern, the hwmon-via-sysfs fan reader, the FAT32-survives-boot install dance via /boot/config/go — are the parts worth lifting even if your stack looks nothing like mine.

Layout

Two rows plus a footer, all running on the Unraid host inside a server-side tmux session. Each panel is titled in its top border, lazygit-style, with the focused one picked out in green:

Region Content Source
system — top ~65%, full width btop --force-utf — CPU/cores/load, mem, usage + live R/W per array disk, eth0 net, processes upstream btop
gpu · containers — bottom ~35%, 50/50 nvtop (left) · ctop -a -s cpu (right, running containers: CPU, mem, PIDs) upstream
Footer — tmux status bar box name + IP · tower-health light (● healthy / ▲ first problem) · fan-status --line (intake/exhaust %+RPM, CPU temp) · clock, every 2s this repo

Colours are terminal-palette indices throughout (btop's TTY theme, tmux colourN), so the whole thing follows your local terminal's theme.

Anything interactive (lazydocker, lazygit, etc.) was deliberately moved out — this view is pure observability.

How it works

The dashboard is a tmux session named dash that lives on the Unraid box:

  • tower/bin/tower-dash (on the box) builds that session — the layout, each pane running its monitor in a restart loop — then attaches. It builds once; every later run re-attaches to the warm session.
  • workstation/tower-dashboard (on the workstation) is the launcher: it spawns a fresh terminal that runs ssh -t <box> tower-dash. That's the whole client side — one SSH connection, no client-side tmux, no layout logic. On Hyprland, launching again focuses the open dashboard window instead of opening a second; the window's app-id is tower-dashboard, for window rules. If SSH fails, the window stays open to show why.

Because the session lives on the box, the monitors stay warm between launches and survive the workstation disconnecting — re-opening is instant. The session lasts until the box reboots (or until tower-dash kill ends it sooner; tower-dash rebuild restarts it to pick up config changes).

The omarchy bar plugin (manifest.json + omarchy/) polls the box with tower/bin/tower-status, a read-only probe piped over one non-interactive SSH request (ssh <box> bash -s < tower-status), so it works without installing anything new on the box. omarchy/status.py turns the probe's records into JSON and decides what needs attention.

Earlier versions built the layout client-side and drove it over send-keys at attach time, then carried a 1-row fan-strip pane that needed resize hooks to stop it scaling into a fat band. Building on the box and moving the strip into tmux's status bar removed all of that.

Why this over the alternatives?

  • vs. the Unraid web UI — the WebGUI lives behind a tab and refreshes slowly. This is one keystroke ("Tower Dashboard" in your launcher) and updates in real time. No browser, no bookmark, no auth.
  • vs. Glances / Beszel / Netdata — those are excellent dashboards that want a daemon on the box, a port, and (usually) a browser. This is no web daemon and no port: just tmux plus the TUI tools you'd run by hand anyway. Read-only by design.
  • vs. ssh root@tower htop — htop is one pane. This is the panels you actually want, laid out at the right sizes, with the right tool for each (btop for storage I/O, nvtop for GPU, ctop for containers, custom script for fan PWM).

Repo layout

manifest.json        # omarchy plugin manifest (the repo root is the plugin)
omarchy/             # omarchy bar plugin
  TowerWidget.qml      # bar icon + popup panel
  Service.qml          # polling
  status.py            # runs tower-status over SSH → JSON snapshot + health rules
  test_status.py       # unit tests (python3 -m unittest)
workstation/         # Files that live on the laptop/desktop
  tower-dashboard      # launcher — spawns a terminal, SSHes in, attaches
  tower-dashboard.svg  # icon — symlinked into ~/.local/share/icons/.../apps/
  Tower Dashboard.desktop.in  # XDG launcher entry template (Exec=@BIN_PATH@)
  ssh-config.snippet   # optional SSH ControlMaster block for ~/.ssh/config
tower/               # Files that live on the Unraid host
  bin/                 # tower-dash, tower-status, tower-health, fan-status, array-status
  config/              # btop.conf, ctop config, sensors-ignore-fans.conf
  go.snippet           # boot-restore lines for /boot/config/go
  README.md            # Unraid-side details
install-workstation.sh # Idempotent installer for the workstation side
install-tower.sh       # Idempotent installer for the tower side (over SSH)
LICENSE              # MIT

Install

You need key-based (non-interactive) SSH from the workstation to the box first — the plugin and launcher both rely on it.

Omarchy

omarchy plugin add https://github.com/BrettKinny/tower-dashboard --enable

That puts the icon in the bar (right section by default). Set the SSH target in the widget's settings if your box isn't [email protected]. Then:

  • Left-click — popup: array + pool disks (usage, temps, errors), parity check status, CPU/RAM/GPU, fans, containers. R refreshes, D / Enter opens the dashboard, Esc closes.
  • Right-click — open the dashboard. Middle-click — refresh.
  • Notifications — a desktop notification when a problem appears (critical, so it stays until dismissed) and a quiet one when it clears. Each problem is announced once — a disk going from 91% to 92% full isn't news — and the box has to be unreachable for three polls in a row before that's announced, so sleep/wake and Wi-Fi blips stay silent. Toggle with the Notifications setting; state lives in ~/.local/state/tower-dashboard/notified.json.
  • IPC — omarchy-shell brettkinny.tower-dashboard dashboard (also toggle, refresh, status), e.g. for a Hyprland keybinding.

Update with omarchy plugin update brettkinny.tower-dashboard, then omarchy restart shell: the shell's hot reload picks up the widget but keeps a cached copy of omarchy/Service.qml (a same-folder QML type), so polling changes only take effect after a restart.

The plugin brings the launcher with it, so you only need install-workstation.sh too if you also want the "Tower Dashboard" app launcher entry and the unraid-dash alias.

Workstation (any desktop)

git clone https://github.com/BrettKinny/tower-dashboard ~/Repos/tower-dashboard
~/Repos/tower-dashboard/install-workstation.sh

The installer is idempotent (safe to re-run): it symlinks the launcher script and icon into the right XDG dirs, renders the .desktop entry from its template with an absolute Exec= path (some launchers — notably walker on omarchy — don't resolve ~/.local/bin from unqualified Exec lines), adds an unraid-dash shell alias to ~/.bashrc, and refreshes the desktop database so launchers pick up the new entry. If you move the checkout, re-run it — the symlinks point into the checkout.

Tower (Unraid)

  1. Install tmux (NerdTools plugin) and nvtop (nvtop plugin) from the Unraid Apps tab.
  2. Drop static btop + ctop binaries at /boot/config/bin/.
  3. Run ./install-tower.sh [root@your-box] from the workstation. It copies the scripts and configs onto the flash and into their live locations, appends any missing go.snippet lines to /boot/config/go (review them first — the fan lines are specific to my board), and builds the tmux-256color terminfo onto the flash if needed. Add --rebuild to restart the warm dash session with the new bits.

See tower/README.md for what goes where and why.

Customize

Everything machine-specific is reachable via env vars or plugin settings, so you should rarely need to fork.

Workstation side (set in ~/.bashrc or shell rc)

Var Default What it does
TOWER_HOST [email protected] SSH target the launcher (and install-tower.sh) connects to. The bar plugin uses its own SSH target setting.
TERMINAL autodetected Override terminal autodetection (otherwise tries xdg-terminal-exec, ghostty, kitty, alacritty, foot, wezterm, gnome-terminal, konsole, xterm in that order)

Plugin health thresholds

What counts as "needs attention" lives at the top of omarchy/status.py: disks ≥ 90% full, array disks ≥ 50°C, pool devices ≥ 70°C, CPU/GPU ≥ 85°C, plus any disabled/missing array disk, unmounted pool, read errors, parity-check errors, stalled fan, unhealthy or restarting container, or a stopped array. Empty slots (including an unassigned parity2) are ignored.

Tower side — fan-status hardware mapping

fan-status defaults match an Asus board with NCT6779 superio + Intel coretemp + 3-intake/2-exhaust fan wiring. To adapt:

ssh [email protected] fan-status --detect   # lists all hwmon chips + their pwm/fan/temp inputs

Then export overrides on the tower side (e.g. in /etc/profile.d/), or edit the defaults at the top of the script:

Var Default Notes
TOWER_INTAKE_CHIP nct6779 Chip name (prefix-matched against /sys/class/hwmon/*/name)
TOWER_EXHAUST_CHIP nct6779 Same chip in most setups
TOWER_TEMP_CHIP coretemp k10temp for AMD
TOWER_INTAKE_NUM 1 pwm1 + fan1_input
TOWER_EXHAUST_NUM 3 pwm3 + fan3_input
TOWER_TEMP_NUM 1 temp1_input

Tower side — btop array disks

Edit disks_filter in tower/config/btop/btop.conf to match your array (Unraid's /etc/fstab doesn't list array mounts, so an explicit filter is required).

SSH

The dashboard uses a single SSH connection — it attaches to the tmux session on the box. workstation/ssh-config.snippet has an optional ControlMaster block; it's not required, but it makes the bar plugin's polls near-free (~100 ms instead of a fresh handshake each time). The launcher pins TERM=xterm-256color for the remote tmux, because the box's RAM-FS won't have terminfo for xterm-ghostty / xterm-kitty and friends.

License

MIT — see LICENSE.

Explore related projects