BOP Blog: Server monitoring with CGI-Script

BytesOfProgress

Server monitoring with CGI-Script

12th August 2024 / 11:15 AM

I wrote a simple web based monitoring tool with a CGI Script and used HTMX for the statistics to refresh automatically.

When looking for an admin interface to use, I noticed that every single one I was able to find, had way too much stuff going on for my use case (too many features i would never use), or they are not FOSS, which I didn't want for my server. This is when I decided that it would make sense to build my own!

So I started researching on how I could get some system statistics and a shell on a website. Then I discovered CGI scripts (Common Gateway Interface): A way to write shell commands inside HTML tags and then let it display with a webserver. This is quite an old technique, introduced, as of my research in the early 1990s, but still enough for my usecase.

The information I found on the use of CGI scripts was mainly focused on the Apache2 webserver, so I had to find a way to use them with Nginx. This took some more research and tinkering but I eventually got it working after some difficulties.

To get it working, I used fastcgi (in debian the package is called "fcgiwrap ") and some changes in my Nginx configuration (/etc/nginx/sites-available/default):

  server {
      listen PORT;
      server_name NAME;

      root /var/www/html;
      index index.html;

      location /cgi-bin/ {
          alias /usr/lib/cgi-bin/; # Default path for cgi scripts.
          fastcgi_pass unix:/var/run/fcgiwrap.socket;
          include fastcgi_params;
          fastcgi_param SCRIPT_FILENAME /usr/lib$fastcgi_script_name;
      }
  }

Particularly I had to add the lines in the "location" block.

Then I had to write the CGI script itself. This is what I wrote:

  #!/bin/sh

  echo "Content-Type: text/html"
  echo ""
  echo "<!DOCTYPE html>"
  echo "<html lang=\"de\">"
  echo "<head>"
  echo "  <meta charset=\"UTF-8\">"
  echo "  <title>BOP Admin Panel</title>"
  echo "  <link rel=\"icon\" type=\"image/x-icon\" href=\"/assets/favicon.png\">"
  echo "  <style>"
  echo "    body { font-family: Arial, sans-serif; color: #ffffff; background-color: #000000; }"
  echo "    .section { margin-bottom: 20px; }"
  echo "    .section h2 { margin: 0; }"
  echo "    pre { color: #ffffff; font-size: 14px; white-space: pre-wrap; }"
  echo "  </style>"
  echo "</head>"
  echo "<body>"

  # CPU Temperature
  echo "<div class='section'>"
  echo "<h2>CPU Temperature:</h2>"
  if command -v sensors >/dev/null 2>&1; then
    echo "<pre>$(sensors | grep Core | awk '{print $1, $2, $3}')</pre>"
  else
    echo "<p>Command 'sensors' not available.</p>"
  fi
  echo "</div>"

  # CPU Usage
  echo "<div class='section'>"
  echo "<h2>CPU Usage:</h2>"
  echo "<pre>"
  (
    # First measurement
    awk '/^cpu[0-9]+/ {print $1, $2+$3+$4+$5+$6, $5}' /proc/stat > /tmp/stat1
    sleep 1
    # Second measurement
    awk '/^cpu[0-9]+/ {print $1, $2+$3+$4+$5+$6, $5}' /proc/stat > /tmp/stat2

    # Calculation & Output
    awk '
      NR==FNR {
        idle_before[$1]=$3
        total_before[$1]=$2
        next
      }
      /^cpu[0-9]+/ {
        idle[$1]=$3
        total[$1]=$2
        if (length(idle_before[$1])) {
          idle_diff = idle[$1] - idle_before[$1]
          total_diff = total[$1] - total_before[$1]
          usage = (total_diff > 0) ? (100 * (1 - (idle_diff / total_diff))) : 0
          core = substr($1, 4)
          printf "Core %d: %.1f%%\n", core, usage
        }
      }' /tmp/stat1 /tmp/stat2
  )
  echo "</pre>"
  echo "</div>"

  # RAM Usage
  echo "<div class='section'>"
  echo "<h2>RAM Usage:</h2>"
  echo "<pre>"
  echo "$(awk '/MemTotal/ {total=$2} /MemAvailable/ {available=$2} END {usage=((total-available)/total)*100; printf "Percent: %.2f%%\n", usage}' /proc/meminfo)"
  echo "$(awk '/MemTotal/ {total=$2} /MemAvailable/ {available=$2} END {printf "MiB: %d / %d MiB\n", (total-available)/1024, total/1024}' /proc/meminfo)"
  echo "</pre>"
  echo "</div>"

  # Uptime
  echo "<div class='section'>"
  echo "<h2>Uptime:</h2>"
  echo "<pre>$(uptime -p | sed 's/up //')</pre>"
  echo "</div>"

  echo "</body>"
  echo "</html>"

  # Removing temporary files.
  rm /tmp/stat1 /tmp/stat2

It displays the CPU Temperature, CPU Usage, RAM Usage in MiB and percentage and the uptime.

I them edited my index.html: I inserted HTMX into it, so that the statistics get refreshed once a second. To do this, I added these few lines:

<script src="https://unpkg.com/htmx.org@2.0.1"></script>

<div hx-get="/cgi-bin/script.sh" hx-swap="innerHTML" hx-trigger="every 1s">
        <!-- Script Content -->
</div>

This creates a div and swaps its content to the content of the CGI-script every one second via HTMX.

Info on CSS, title, etc: I figured out, to change the title, favicon and CSS of your index.html, you might have to change it in the CGI-script instead of your HTML style tag or external stylesheet if you plan on doing something similar.

To have shell access in my small "admin panel", I installed ShellInABox and created an iframe that contains it into my HTML. This is what it looks like in the end:


23rd August 2024 / 09:15 AM

I did not really like the look of the admin panel, so I changed it: I let the statistics display next to each other and put them into boxes with borders, so that they can be differentiated from each other much easier.


I also added the disk usage to the statistics. I also planned on adding disk I/O and netin / netout statistics, but I did not figure out how to grab that info in a proper way yet.




back