Skip to content

Latest commit

 

History

12 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

LVM Manager TUI (real, live LVM data)

This talks to your actual system LVM via vgs, pvs, lvs, and lsblk, and its action keys run real, mutating lvm2 commands (lvcreate, lvextend, lvremove, vgremove, snapshots). There is no mock/demo data in this version. lvm-tui

Project structure

lvm-tui/
├── go.mod
├── cmd/
│   └── lvmtui/
│       └── main.go          — thin entrypoint: builds a ui.App and runs it
└── internal/
    ├── model/                — plain data types, no I/O
    │   ├── model.go          — VolumeGroup/PhysicalVolume/LogicalVolume/Job/LogEntry,
    │   │                       Status()/UsedBytes()/UsedPercent(), HumanBytes(), CapacityBar()
    │   └── model_test.go
    ├── lvm/                   — real system I/O (the only package that shells out)
    │   ├── lvm.go             — LoadVolumeGroups(): vgs/pvs/lvs/lsblk → []model.VolumeGroup
    │   ├── actions.go         — CreateLV/ExtendLV/RemoveLV/RemoveVG/SnapshotLV/... (mutating)
    │   ├── lvm_test.go
    │   ├── actions_test.go
    │   └── testutil_test.go   — shared fake-PATH-binary test helper
    └── ui/                    — tview layout, panels, forms, key bindings
        ├── app.go             — ui.New() / (*App).Run(); everything you saw rendered
        └── app_test.go        — tests for the non-widget logic (job/log tracking, selection)

Why this layout, and what depends on what:

  • model has zero dependencies beyond the standard library — it's just data plus pure formatting/derivation logic (Status(), HumanBytes(), etc.). Nothing here can accidentally shell out to a real command or touch a terminal.
  • lvm depends only on model (and os/exec). It's the only package that runs real commands. If you ever want a different frontend (a web UI, a CLI that just prints a table, a Prometheus exporter), this is the package you'd reuse as-is.
  • ui depends on model and lvm, and on tview/tcell. It has no idea how lvm.LoadVolumeGroups() gets its data — it just calls the function and renders what comes back. This is what made the earlier construction-order panic easy to isolate and fix, and it's why the job/log/selection logic in app_test.go can be tested without spinning up any actual terminal widgets.
  • cmd/lvmtui is deliberately tiny (10 lines) — all it does is call ui.New().Run() and report a fatal error if the event loop exits abnormally. This is the standard Go convention: cmd/ holds thin entrypoints, internal/ holds everything an external project can't import.

This also means go doc lvmtui/internal/lvm, go doc lvmtui/internal/model, etc. each show a focused, relevant API instead of one 700-line file's worth of unrelated exported names.

Requirements

  • Linux with lvm2 installed (vgs/pvs/lvs/lvcreate/etc. on PATH)
  • Root privileges — LVM metadata and device nodes require this. Run with sudo.
  • Go 1.21+ to build

Build & run

cd lvm-tui
go mod tidy
go build -o lvmtui ./cmd/lvmtui
sudo ./lvmtui

Running it without root will very likely fail to read anything (you'll see the error in the header/status panel and Logs) — LVM commands need root even for read-only queries in most setups.

What's real here

  • Volume Groups / Physical Volumes / Logical Volumes panels — populated by parsing vgs/pvs/lvs --reportformat json --units b --nosuffix, joined together in internal/lvm/lvm.go.
  • Filesystem type / mount point / used space per LV — from lsblk -J -b -o NAME,KNAME,FSTYPE,MOUNTPOINT,FSUSED,FSSIZE, matched to each LV by resolving its device-mapper symlink (e.g. /dev/vg_system/root) down to its kernel device name (e.g. dm-0).
  • VG status (Healthy/Warning/Critical) — computed from vg_attr (a p flag means a PV is missing → Critical) and free-space percentage (<10% free → Warning), same thresholds as your screenshot.
  • Actions (n/e/s/x/X/c) run real commands via os/exec (internal/lvm/actions.go):
    • n — Create LV: lvcreate -y -n <name> -L <size> <vg>
    • e — Extend LV: lvextend -r -L +<size> <lvpath> (-r grows the filesystem online)
    • s — Snapshot: lvcreate -y -s -n <name> -L <size> <lvpath>
    • x — Remove LV (confirmation required): lvremove -y <lvpath>
    • X — Remove VG (confirmation required): vgremove -y <vg>
    • c — Check VG: vgck <vg>
    • r — Refresh: re-runs vgs/pvs/lvs/lsblk from scratch
  • Jobs panel — a real history of the actions you've run in this session and whether they succeeded or failed (not simulated progress bars).
  • Logs panel — real log lines: what was loaded, what actions ran, and any command errors (stderr) verbatim.

Testing

go test ./...          # run all tests
go test -v ./...       # verbose
go test -race ./...    # with the race detector (relevant since actions run in a goroutine)
go test -cover ./...   # coverage summary

32 tests across the three packages, all passing, no go vet issues, clean under -race.

How the tests work without a real LVM system: internal/lvm shells out to real binaries (vgs, pvs, lvs, lsblk, lvcreate, ...). The tests never touch your real system — each test prepends a temp directory of fake executable scripts onto PATH (see testutil_test.go), so:

  • Read-path tests (lvm_test.go) feed the parser realistic LVM2 JSON (the same schema --reportformat json really emits) and assert VG/PV/LV joins, status thresholds, and error handling (command missing, nonzero exit, invalid JSON).
  • Action tests (actions_test.go) use fake binaries that just echo their arguments back, so each test asserts the exact flags/arguments (e.g. CreateLV really invokes lvcreate -y -n <name> -L <size> <vg>) without ever running a real mutating command.
  • model_test.go / ui/app_test.go cover pure logic: byte formatting, VG status derivation, job history, log buffer, and current-selection helpers — no fake binaries needed, and no tview widgets constructed.

ui/app.go's widget-building code (the actual tview.Flex/tview.Table layout) isn't unit tested — that's better suited to manual/visual testing than assertions — but everything it calls into that isn't pure widget plumbing (currentVG, addJob, data prep before SetText, etc.) is tested where it's decoupled from rendering.

One real bug this test suite caught and fixed: CapacityBar capped the rendered bar width for percentages over 100%, but printed the raw (uncapped) percentage text — e.g. 150.0% instead of 100.0%. Now both are clamped consistently.

Known limitations / things to sanity-check on your system

  • lvextend -r requires fsadm (part of lvm2) and support for online resize of your filesystem (works for ext2/3/4 and XFS growth; XFS can't shrink).
  • There's no lvreduce wired to a key yet (shrinking is riskier — the ReduceLV function exists in internal/lvm/actions.go if you want to wire it up, but I intentionally didn't bind it to a shortcut).
  • pvmove and "change VG tag" functions exist in internal/lvm/actions.go but aren't wired into the UI — straightforward to add a form for them the same way Create/Extend/Snapshot are done in internal/ui/app.go.
  • Destructive actions (x, X) require an explicit "Confirm" click/press in a modal first.
  • I don't have a real LVM system in my own environment, so while the read-path parsing and the exact arguments of every mutating command are verified by the test suite, I haven't run this end-to-end against real disks. Try it on a disposable VG before trusting it with real data.

About

This talks to your actual system LVM via `vgs`, `pvs`, `lvs`, and `lsblk`, and its action keys run real, mutating `lvm2` commands (`lvcreate`, `lvextend`, `lvremove`, `vgremove`, snapshots). There is no mock/demo data in this version.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages