Server Stats widget screenshot

Script

Server Stats

by Brett Terpstra

Show live load, memory, disk, and uptime from a remote server in a compact Terminal Widget table. The script SSHes in once, reads standard Linux host metrics, and updates a named target with --table (fit-text, centered layout).

Works with any Linux or Linux-like host you can reach over SSH—VPS, dedicated, cloud instance, or shared hosting with shell access (DreamHost, DigitalOcean, Linode, home lab, etc.). If you can ssh in without a password prompt, you can drive this widget. The remote side only needs common tools (/proc, free, df); no agent or extra software on the server.

Requirements

  • Terminal Widget (terminal-widget on your PATH)
  • Passwordless SSH to the server (key-based auth; BatchMode)
  • A widget whose Target name matches the script’s TARGET (default server-stats)

SSH setup

Pick one of these in the script’s # === CONFIG === block (redact before sharing):

In ~/.ssh/config:

Host myserver
  HostName your.example.com
  User youruser
  IdentityFile ~/.ssh/id_ed25519

Then in the script:

SSH_ALIAS="myserver"
# leave SSH_USER / SSH_HOST empty

Test: ssh myserver true should succeed with no password prompt.

Option B — User, host, and optional key

SSH_ALIAS=""
SSH_USER="youruser"
SSH_HOST="your.example.com"
SSH_KEY="~/.ssh/id_ed25519"   # optional; empty uses the agent / default keys

Also set:

TARGET="server-stats"   # must match Edit Widget → Target name
TW="/opt/homebrew/bin/terminal-widget"

What it shows

Row Value
load 1 / 5 / 15-minute load averages
mem used/total MB and % free (from available)
disk used/total MB on $HOME and % free
uptime days since boot

Run / schedule

chmod +x ~/scripts/widget-server-stats.sh
~/scripts/widget-server-stats.sh

Refresh on a timer with LaunchAgent, cron, or Shortcuts. On SSH failure the script exits without wiping the last good widget state.

Notes

  • Metrics come from /proc and free (typical Linux). Other Unix variants may need small tweaks to the remote one-liner.
  • Keep secrets out of shared copies: only publish a config block with placeholders, never your real alias, user, or key path.

Script (server-stats.sh)

#!/usr/bin/env bash
# Refresh a Terminal Widget table with remote server stats over SSH.
# Redact the CONFIG block before sharing publicly.

set -euo pipefail

# === CONFIG (edit me; redact before sharing) ===
TARGET="server-stats"
TW="/opt/homebrew/bin/terminal-widget"

# Option 1: SSH config Host alias (preferred if set)
SSH_ALIAS="dh"

# Option 2: explicit connection (used when SSH_ALIAS is empty)
SSH_USER="youruser"
SSH_HOST="your.example.com"
SSH_KEY="" # optional identity file; empty = default agent/keys
# === END CONFIG ===

ssh_base=(ssh -o BatchMode=yes -o ConnectTimeout=10)

if [[ -n "${SSH_ALIAS}" ]]; then
  ssh_cmd=("${ssh_base[@]}" "${SSH_ALIAS}")
else
  if [[ -z "${SSH_USER}" || -z "${SSH_HOST}" ]]; then
    echo "widget-server-stats: set SSH_ALIAS or both SSH_USER and SSH_HOST" >&2
    exit 1
  fi
  if [[ -n "${SSH_KEY}" ]]; then
    ssh_cmd=("${ssh_base[@]}" -i "${SSH_KEY}" "${SSH_USER}@${SSH_HOST}")
  else
    ssh_cmd=("${ssh_base[@]}" "${SSH_USER}@${SSH_HOST}")
  fi
fi

remote_script='printf "load="; cut -d" " -f1-3 /proc/loadavg
free -m | awk "/^Mem:/{printf \"mem_used=%s\nmem_total=%s\nmem_avail=%s\n\", \$3, \$2, \$7}"
df -P "$HOME" | awk "NR==2{printf \"disk_used_kb=%s\ndisk_total_kb=%s\ndisk_used_pct=%s\n\", \$3, \$2, \$5}"
awk "{printf \"uptime_days=%d\n\", int(\$1/86400)}" /proc/uptime'

if ! metrics="$("${ssh_cmd[@]}" "${remote_script}")"; then
  echo "widget-server-stats: SSH failed" >&2
  exit 1
fi

load="" mem_used="" mem_total="" mem_avail=""
disk_used_kb="" disk_total_kb="" disk_used_pct="" uptime_days=""

while IFS='=' read -r key value; do
  case "$key" in
    load|mem_used|mem_total|mem_avail|disk_used_kb|disk_total_kb|disk_used_pct|uptime_days)
      printf -v "$key" '%s' "$value"
      ;;
  esac
done <<< "$metrics"

: "${load:?missing load}"
: "${mem_used:?missing mem_used}"
: "${mem_total:?missing mem_total}"
: "${mem_avail:?missing mem_avail}"
: "${disk_used_kb:?missing disk_used_kb}"
: "${disk_total_kb:?missing disk_total_kb}"
: "${disk_used_pct:?missing disk_used_pct}"
: "${uptime_days:?missing uptime_days}"

load_fmt="${load// /, }"

mem_free_pct=$(( mem_avail * 100 / mem_total ))
mem_fmt="${mem_used}/${mem_total} (${mem_free_pct}% free)"

disk_used_mb=$(( disk_used_kb / 1024 ))
disk_total_mb=$(( disk_total_kb / 1024 ))
disk_used_pct_num="${disk_used_pct%%%}"
disk_free_pct=$(( 100 - disk_used_pct_num ))
disk_fmt="${disk_used_mb}/${disk_total_mb} (${disk_free_pct}% free)"

uptime_fmt="${uptime_days} days"

tsv=$(printf 'load\t%s\nmem\t%s\ndisk\t%s\nuptime\t%s\n' \
  "$load_fmt" "$mem_fmt" "$disk_fmt" "$uptime_fmt")

printf '%s' "$tsv" | "$TW" --target "$TARGET" --table - --no-header \
  --grid zebra-row --table-align right,left --table-layout fill --center-table --fit-text --background-image "https://images.unsplash.com/photo-1431440869543-efaf3388c585?q=80&w=2940&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" --fg ffffff --font Menlo

Download script

Running in the background with launchd

Suggested interval: 15 minutes (StartInterval = 900 seconds).

Save the script from this recipe to ~/bin/server-stats.sh, then create a Launch Agent plist:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>Label</key>
  <string>com.terminalwidget.server-stats</string>
  <key>ProgramArguments</key>
  <array>
    <string>/bin/bash</string>
    <string>~/bin/server-stats.sh</string>
  </array>
  <key>RunAtLoad</key>
  <true/>
  <key>StartInterval</key>
  <integer>900</integer>
  <key>StandardOutPath</key>
  <string>/tmp/com.terminalwidget.server-stats.log</string>
  <key>StandardErrorPath</key>
  <string>/tmp/com.terminalwidget.server-stats.err</string>
</dict>
</plist>

From Terminal:

mkdir -p ~/bin ~/Library/LaunchAgents
# Save your script to ~/bin/server-stats.sh
cat > ~/Library/LaunchAgents/com.terminalwidget.server-stats.plist <<'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>Label</key>
  <string>com.terminalwidget.server-stats</string>
  <key>ProgramArguments</key>
  <array>
    <string>/bin/bash</string>
    <string>~/bin/server-stats.sh</string>
  </array>
  <key>RunAtLoad</key>
  <true/>
  <key>StartInterval</key>
  <integer>900</integer>
  <key>StandardOutPath</key>
  <string>/tmp/com.terminalwidget.server-stats.log</string>
  <key>StandardErrorPath</key>
  <string>/tmp/com.terminalwidget.server-stats.err</string>
</dict>
</plist>
EOF
launchctl bootstrap "gui/$(id -u)" ~/Library/LaunchAgents/com.terminalwidget.server-stats.plist

For a GUI editor and troubleshooting, see LaunchControl from soma-zone.

← All recipes