A terminal dashboard for monitoring and managing a fleet of virtual private servers. Written in C++ with ncurses, it displays live stats (uptime, CPU load, memory, disk, pending updates, reboot status) fetched concurrently over SSH, with a sortable, scrollable server list and an interactive details pane.
- Live data over SSH — uptime, CPU load, memory, swap, disk usage (up to 3 devices per server), pending package updates, and reboot-required status are fetched in background threads and refresh automatically
- Sortable list — press
sto sort by any column (ID, nickname, IP, OS, cost, load, memory, disk, uptime, updates, reboot status, and more); toggle ascending/descending by pressing the same key again - Details pane — selecting a server shows a full breakdown in the lower third of the screen, including all disk devices with mount points
- Edit in place —
Enteropens an edit form for any server;a/+adds a new one;ddeletes with optional reason logging - Jump host support — each server can specify a jump host in
servers.jsonfor SSH tunneling - Local-machine aware — when vpsmc runs on one of the monitored servers, it detects this and fetches that server's data locally instead of looping through SSH
- SSH key verification —
check-ssh.shcross-checks live host key fingerprints against the entries inservers.jsonand auto-adds verified keys to~/.ssh/known_hosts - Rebooter utility —
rebooteris a companion ncurses tool focused on the update/reboot cycle: it lists servers needing attention, checks for logged-in users, open files, and other pre-reboot conditions - Automatic JSON backups — every save archives the previous
servers.jsonasservers.json.v1,.v2, etc. - 256-colour UI — zebra-striped rows, colour-coded disk usage (green / white / magenta / red by threshold), status indicators
| Dependency | Purpose |
|---|---|
g++ with C++17 |
Compilation |
libncursesw + dev headers |
Terminal UI (-lncursesw) |
libpthread |
Background SSH threads |
jq |
Used by check-ssh.sh |
ssh, ssh-keyscan, ssh-keygen |
Remote data fetching and key verification |
On Debian/Ubuntu:
sudo apt install build-essential libncursesw5-dev jqgit clone https://github.com/yourname/vpsmc.git
cd vpsmc
makeTo install binaries to /usr/local/bin/ and set up ~/vpsmc/ with example data:
make installTo build only a specific tool:
make vpsmc
make rebootervpsmc looks for its configuration file in this order:
./vpsmc.conf.json(current directory)~/vpsmc/vpsmc.conf.json
The bundled vpsmc.conf.json uses relative paths (./servers.json, ./logs/). There is no servers.json in the repo — on first run the built-in wizard prompts you to import servers from ~/.ssh/config or add them manually.
make install copies the conf to ~/vpsmc/vpsmc.conf.json (rewriting paths to ~/vpsmc/) and installs the binaries to /usr/local/bin/. After that, servers.json lives in ~/vpsmc/ and is never tracked by git.
The installed conf looks like:
{
"vpsmc_config": {
"paths": {
"servers_json": "~/vpsmc/servers.json",
"log_directory": "~/vpsmc/logs/",
"exchange_rates_json": "~/vpsmc/xch-rates.USD.json"
},
"settings": {
"default_base_currency": "USD",
"ssh_connect_timeout_seconds": 10
}
}
}Each server is an entry in the vpsmc.machines array. The SSH nick must match an alias in your ~/.ssh/config so that ssh nick works without a password prompt.
{
"vpsmc": {
"machines": [
{
"id": 1,
"nick": "web1",
"canonical_name": "web1.example.com",
"network": { "ipv4": "192.0.2.1", "ipv6": "" },
"ISP": "Linode",
"ISP_code": "LN",
"monthly_cost_cents": 1000,
"cost_currency": "USD",
"purpose": "Web, Frontend",
"jump_host": "",
"os": { "name": "Ubuntu", "version": "24.04" },
"specs": {
"cpu_cores": 2,
"memory_gb": 3.814,
"swap_gb": 0.0,
"disk_used_percent": 34.0,
"disk1_device": "/dev/sda",
"disk1_mount": "/",
"disk2_used_percent": -1.0,
"disk2_device": "",
"disk2_mount": "",
"disk3_used_percent": -1.0,
"disk3_device": "",
"disk3_mount": ""
},
"public_keys": [
{ "type": "RSA", "MD5": "aa:bb:...", "SHA-256": "base64==" },
{ "type": "ECDSA", "MD5": "11:22:...", "SHA-256": "base64==" },
{ "type": "ED25519", "MD5": "99:88:...", "SHA-256": "base64==" }
],
"reboot_required": false,
"updates": { "count": 0, "status": "none" },
"flags": { "c4": false, "o2": false, "s3": false },
"tags": [],
"status": "unknown",
"last_checked": ""
}
]
}
}See servers.json.example in the repo root for a complete worked example. Use check-ssh.sh to obtain and verify fingerprints for new servers before adding them.
Each nick in servers.json must have a corresponding entry in ~/.ssh/config:
Host web1
HostName web1.example.com
User root
IdentityFile ~/.ssh/id_ed25519
Port 22
For servers accessed via a jump host, set jump_host in servers.json to the nick of the bastion and add the appropriate ProxyJump or ProxyCommand to ~/.ssh/config.
Run check-ssh.sh to verify connectivity and fingerprints for all servers in one pass:
./check-ssh.sh| Key | Action |
|---|---|
j / ↓ |
Move selection down |
k / ↑ |
Move selection up |
Enter |
Edit selected server |
a / + |
Add new server |
d / Del |
Delete selected server (with confirmation) |
s |
Open sort key selector |
r |
Re-fetch live data for selected server |
R |
Re-fetch live data for all servers |
q |
Quit |
| Key | Action |
|---|---|
j / ↓ / k / ↑ |
Navigate server list |
Tab |
Cycle detail view (users, open files, locks, ...) |
u / a |
Filter: updates-only / all |
r |
Re-check selected server |
R |
Re-check all servers |
q |
Quit |
vpsmc/
├── vpsmc.cpp # Main dashboard (ncurses TUI)
├── vpsmc_lib.cpp # Shared library: JSON I/O, SSH fetchers, config
├── vpsmc_lib.h # Shared library header
├── rebooter.cpp # Reboot/update management companion tool
├── Makefile
├── check-ssh.sh # SSH connectivity and fingerprint verifier
├── servers.json.example # Example fleet structure (reference only)
├── servers.json # Your server fleet — created by wizard, never committed
├── vpsmc.conf.json # Application configuration
└── nlohmann/
└── json.hpp # JSON library (header-only, bundled)
- 256-colour terminal required. Set
TERM=xterm-256colorif your terminal does not do this automatically. - Wide character support required. The build links against
libncursesw(the wide-character variant of ncurses) for proper Unicode rendering. - Static data (specs, cost, OS) is read from
servers.jsonat startup. Live data (uptime, CPU load, disk, updates) is fetched over SSH each time the tool starts or whenr/Ris pressed. - The
exchange_rates_jsonpath in the config is optional; it is used by the separatexch-normalizerutility (not included here) to normalise costs across currencies.