Home-made system monitor

There are already a lot of tools for monitoring your system, so why did I want to make my own?

I have a couple of remote systems, and I want to have, from time to time, an overview. Actually, the same holds for the system in front of me.

A program like glances, Grafana, …​ almost covers my needs. They have a couple of drawbacks.

First, those solution are not exactly small, it feels like cracking a nut with a sledgehammer.

Installing glances from the debian repository on the system I use daily, which is by no way a minimal system, would install 45 additional packages (mostly python packages), downloads 54 MB of data, and takes 211 MB on disk.

It is a lot of data and packages, especially considering that everything that glances shows me is already available through other tools and interfaces.

For a minimal system, especially those with small drives, 200MB of additional space is not really acceptable, which is why I on some system I go the extra mile to minimize the installation footprint. For comparison, my personal live ISO, with a graphical interface and multiple tools preinstalled, is 760MB big.

I’m not sure about how much space tools like grafana use, since it has not been packaged in the main Debian repository, I avoided it until now.

The second issue I have with those solutions is that they are executed all the time. While the resource usage might be negligible on most platforms, I do not need to monitor the computer constantly. For my needs, a cron job is more than enough. On the other hand, being occasionally able to see some statistics in real-time, for example, network, disk, and CPU usage, is a very useful feature my hand-made solution is missing.

If I need to interact with the system, I currently just ssh into the system and fire up tools like htop or nmon.

Creating my own system monitor tool was extremely easy. I decided to store a report in an HTML file, in particular because I wanted collapsible elements, tables, and eventually some graphic elements like a graph.

This was the first revision of my script:

report.sh
#!/bin/bash

set -o errexit
set -o nounset

OUTPUT="$1";

HAS_AHA="false";
if command -v "aha" >/dev/null 2>&1; then :;
  HAS_AHA="true";
fi

exec_cmd(){
  if ! command -v "$1" >/dev/null 2>&1; then :;
    printf 'command %s not found' "$1";
  elif [ "$HAS_AHA" = "true" ]; then :;
    FORCE_COLOR=1 "$@" 2>&1 | aha --no-header --ignore-cr;
  else :;
    FORCE_COLOR=0 "$@" 2>&1;
  fi
}

printf '<!DOCTYPE html>
<html lang=en>
<head>
 <meta charset=utf-8>
 <meta name=viewport content="width=device-width, initial-scale=1">
</head>
<body style="
 background-color: Canvas;
 color: CanvasText;
 color-scheme: light dark;
"><h1>System Report</h1>' > "$OUTPUT";

# quick overview of system: version, peripherals, ...
printf '<h2>System info</h2>' >>"$OUTPUT";
printf '<details><summary>/etc/os-release</summary><pre><code>%s</code></pre></details>' "$(exec_cmd cat /etc/os-release)">>"$OUTPUT"
printf '<details><summary>uname -a</summary><pre><code>%s</code></pre></details>' "$(exec_cmd uname -a)">>"$OUTPUT";
printf '<details><summary>hostname</summary><pre><code>%s</code></pre></details>' "$(exec_cmd hostname )">>"$OUTPUT";
printf '<details><summary>lsusb --tree</summary><pre><code>%s</code></pre></details>' "$(exec_cmd lsusb --tree)">>"$OUTPUT";
printf '<details><summary>lsusb --verbose</summary><pre><code>%s</code></pre></details>' "$(exec_cmd lsusb --verbose )">>"$OUTPUT";
printf '<details><summary>/proc/self/mountinfo</summary><pre><code>%s</code></pre></details>' "$(exec_cmd cat /proc/self/mountinfo )">>"$OUTPUT";


printf '<h2>Disk</h2>' >>"$OUTPUT";
printf '<details><summary>smartctl</summary>'>>"$OUTPUT";
if [ ! -f /sbin/smartctl ]; then :;
  printf '/sbin/smartctl not installed' >>"$OUTPUT";
else
  DEVICES=$(/sbin/smartctl --scan | awk '{print $1}');
  printf '<ul>'>>"$OUTPUT";
  # should be doable also without /sbin/smartctl
  printf '%s' "$DEVICES" | xargs --delimiter='\n' -n1 printf '<li>%s</li>' >>"$OUTPUT";
  printf '</ul></details>' >>"$OUTPUT";

  for i in $DEVICES; do :;
    printf '<details><summary>smartctl %s</summary><pre><code>%s</code></pre></details>' "$i" "$(exec_cmd sudo smartctl -a "$i")">>"$OUTPUT";
  done
 fi


printf '<h2>Network</h2>' >>"$OUTPUT";
printf '<details><summary>ip address</summary><pre><code>%s</code></pre></details>' "$(exec_cmd ip address )">>"$OUTPUT";

printf '</body>\n' >> "$OUTPUT";

The script is extremely basic, but it has multiple advantages.

In particular, it is easy to expand, as long as the information can be queried from the command line.

Tools like Grafana are more complex to configure and expand.

I did not bother to parse the output of most commands; this would mean I need to handle errors. I just dump the output in <code> blocks. Granted, it might not be optimal, but it is good enough, and since the code blocks are collapsed by default, one can navigate from one section to another relatively fast.

Since it is a static document, it can also be opened from lynx from the command line.

This is a snippet of the beginning of the system report:

System Report

System info

/etc/os-release
PRETTY_NAME="Debian GNU/Linux forky/sid"
NAME="Debian GNU/Linux"
VERSION_CODENAME=forky
ID=debian
HOME_URL="https://www.debian.org/"
SUPPORT_URL="https://www.debian.org/support"
BUG_REPORT_URL="https://bugs.debian.org/"
uname -a
Linux fekir-pc 6.18.15+deb14-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.18.15-1 (2026-02-27) x86_64 GNU/Linux
hostname
fekir-pc

Thanks to aha πŸ—„️, it is also possible to convert dedicated escape sequences to HTML colors; for example, this is the begin of the rendered output of echo n | fwupdmgr security --no-history | aha --no-header --ignore-cr:

<pre><code>Idle…: 0%
Host Security ID: <span style="font-weight:bold;">HSI:0! (v2.0.20)</span>

<span style="font-weight:bold;">HSI-1</span>
βœ” BIOS firmware updates:         <span style="color:green;"></span><span style="font-weight:bold;color:green;">Enabled</span>
βœ” csme manufacturing mode:       <span style="color:green;"></span><span style="font-weight:bold;color:green;">Locked</span>
βœ” csme override:                 <span style="color:green;"></span><span style="font-weight:bold;color:green;">Locked</span>
βœ” Platform debugging:            <span style="color:green;"></span><span style="font-weight:bold;color:green;">Disabled</span>
βœ” SPI write:                     <span style="color:green;"></span><span style="font-weight:bold;color:green;">Disabled</span>
βœ” Supported CPU:                 <span style="color:green;"></span><span style="font-weight:bold;color:green;">Valid</span>
βœ” UEFI bootservice variables:    <span style="color:green;"></span><span style="font-weight:bold;color:green;">Locked</span>
✘ csme v0:12.0.8.1123:           <span style="color:red;"></span><span style="font-weight:bold;color:red;">Invalid</span>
✘ SPI lock:                      <span style="color:red;"></span><span style="font-weight:bold;color:red;">Disabled</span>
✘ SPI BIOS region:               <span style="color:red;"></span><span style="font-weight:bold;color:red;">Unlocked</span>
✘ TPM v2.0:                      <span style="color:red;"></span><span style="font-weight:bold;color:red;">Not found</span>

...
</code></pre>

If the Content-Security-Policy permits inline styling (which is normally the case for local documents), then the output will be rendered colored by the browser.

The internal function exec_cmd redirects stderr to stdout, so that both the normal output and error are captured in the HTML document. If aha is available, exec_cmd sets FORCE_COLOR=1 and uses aha to convert the colored output to appropriate HTML code. If aha is not available, it sets FORCE_COLOR=0, as there is little reason to store the escape sequences in an HTML file.

The environment variable is unfortunately not recognized by all programs; for example, systemctl has its own environment variables: SYSTEMD_COLORS and SYSTEMD_LOG_COLOR. But even when those are set, the output of systemctl is not colored, as the application ignores the environment variable if the output is piped somewhere.

The workaround is to use script:

exec_systemctl(){
  if [ "$HAS_AHA" = "true" ]; then :;
    # even with "SYSTEMD_COLORS=true SYSTEMD_LOG_COLOR=true" systemctl does not print colored output, thus use a script to let systemctl think it has a tty
    cmd=$(printf '%q ' "$@")
    script --quiet --command "systemctl --full --no-pager $cmd" 2>&1 | aha --no-header --ignore-cr;
  else :;
    systemctl --full --no-pager "$@";
  fi
}

# Usage
printf '<details><summary>systemctl services  </summary><pre><code>%s</code></pre></details>' "$(exec_systemctl list-unit-files            )">>"$OUTPUT";

The only thing to pay attention to is that in POSIX sh, printf "%q" is not defined.

I should properly escape the outputs of the command before pasting them in an HTML file. Replacing the <, >, and & characters should be enough, and this can be done in exec_cmd and exec_systemctl by piping the output to sed, before piping the result to aha:

$COMMAND 2>&1 | sed -e 's/&/\&amp;/g' -e 's/</\&lt;/g' -e 's/>/\&gt;/g' | aha --no-header --ignore-cr;

Compared to other solutions, my script is extremely minimal. It takes less than a cuople of kilobytes on my drive, does not consume any resources except when executed on demand.

The results can be versioned, and if a webserver is running, it can be exposed the whole time and accessed with a browser directly.


If you have questions, comments, or found typos, the notes are not clear, or there are some errors; then just contact me.