DepUnPr

DIY RaspberryPI homeserver dashboard - part 1

A simple HTML homepage for my RPI gets out of my hands

Some back story

I have a little server at home. It’s a RaspberryPI 4, with an m.2 SSD disk in an USB enclosure as system disk and two mechanical SSD laptop disks as data store, connected via an USB SATA dock.

It’s not much, but it’s enough for me. It runs Debian 12 and it runs some services on my home network:

Most of this services have a web interface which in my network are served on dedicated subdomains of casa.rpi . All of those are resolved by pi-hole to my RPI.

How it started

I had lighttpd doing his job as proxy to serve services subdomains, but main domain was sitting here, empty.

I then thought I could write a simple html page with links to various services, as an entry point.

It was a quick page to build: a icon (service favicon linked directly) and a link for each services, some quick css and it was done.

The page as it was

And I didn’t touch it for some years.

The rewrite

As usual, things starts with a simple need.

The page color scheme was fixed and dark, and I wanted it to follow system preferences for light/dark scheme.

So I replaced most of the CSS of the page with picocss , a simple CSS framework which comes with dark/light color scheme in a 81K minified css file.

After some fiddling with html and some custom css this is how it become:

New page with light/dark styles

Same overall layout, better visual outcome (imho) and dark/light support.

Links animate on mouseover/click, and gets focus outline.

I like it ๐Ÿ™‚

As said, icons comes from service web interface favicon. This has the nice side effect that if the service is down, icon can’t be load and become a quick visual clue that something doesn’t work.

A nice touch is the logo image: it’s an SVG directly embedded into the page inside the <h1>, without any fill color defined, which is then set by picocss. This way the logo follows the color scheme!

I could have stopped here, but if I had, you wouldn’t be reading this post.

Stats!

So I looked at this page and I thought: wouldn’t be nice to have some insight on how the system is doing just in this page?

So here we go collecting stats!

The idea is to install collectd and use rrdtool to generate some graphs and then show the grap in the page.

# apt install collectd
# apd install rrdtool
# systemctl status collectd

By default collectd collect more stats than we need, but for now we leave the configuration as it is.

To create the graph I’m using rrdtool graph command. It’s not easy to use, but with some tutorials and the documentation, I ended up with some nice results.

My page is /var/www/html/index.html. I will put my graphs in subfolder stats/ , with a bash script to generate them.

NOTE

yes, the script will be readable via webserver, but this web server is not on the internet. Everything I’m doing here should not be done on a public-facing server

rrdtool reads data from .rrd files. collectd write collected data to .rrd files in /var/lib/collectd/rrd/localhost. Here there is a folder for each plugins, and inside those an .rrd folder for each metric.

# ls /var/lib/collectd/rrd/localhost
cpu-0
cpu-1
cpu-2
cpu-3
cpufreq-0
cpufreq-1
cpufreq-2
cpufreq-3
cpusleep
df-boot-firmware
df-media-sda1
df-media-sdb1
df-root
[...]
interface-eth0
interface-lo
interface-wg0
interface-wlan0
irq
load
memory
[...]
# ls /var/lib/collectd/rrd/localhost/cpu-0
cpu-idle.rrd
cpu-interrupt.rrd
cpu-nice.rrd
cpu-softirq.rrd
cpu-steal.rrd
cpu-system.rrd
cpu-user.rrd
cpu-wait.rrd

Each .rrd file contains timestamped data for one or more values. We can inspect them with rrdtool info <file.rrd>`

# cd /var/lib/collectd/rrd/localhost
# rrdtool info cpu-0/cpu-user.rrd
filename = "cpu-0/cpu-user.rrd"
rrd_version = "0003"
step = 10
last_update = 1737033603
header_size = 3496
ds[value].index = 0
ds[value].type = "DERIVE"
ds[value].minimal_heartbeat = 20
ds[value].min = 0,0000000000e+00
ds[value].max = NaN
ds[value].last_ds = "832605"
ds[value].value = 2,1000000000e+00
ds[value].unknown_sec = 0
rra[0].cf = "AVERAGE"
rra[0].rows = 1200
rra[0].cur_row = 258
rra[0].pdp_per_row = 1
rra[0].xff = 1,0000000000e-01
rra[0].cdp_prep[0].value = NaN
rra[0].cdp_prep[0].unknown_datapoints = 0
rra[1].cf = "MIN"
rra[1].rows = 1200
rra[1].cur_row = 177
rra[1].pdp_per_row = 1
rra[1].xff = 1,0000000000e-01
rra[1].cdp_prep[0].value = NaN
rra[1].cdp_prep[0].unknown_datapoints = 0
rra[2].cf = "MAX"
rra[2].rows = 1200
rra[2].cur_row = 456
rra[2].pdp_per_row = 1
rra[2].xff = 1,0000000000e-01
rra[2].cdp_prep[0].value = NaN
rra[2].cdp_prep[0].unknown_datapoints = 0
[...]

… and many more lines. What we are looking for here is ds[value]. This file has one variable, named value.

# rrdtool info interface-eth0/if_packets.rrd
filename = "interface-eth0/if_packets.rrd"
rrd_version = "0003"
step = 10
last_update = 1737034003
header_size = 4928
ds[rx].index = 0
ds[rx].type = "DERIVE"
ds[rx].minimal_heartbeat = 20
ds[rx].min = 0,0000000000e+00
ds[rx].max = NaN
ds[rx].last_ds = "40872202"
ds[rx].value = 1,0290000000e+02
ds[rx].unknown_sec = 0
ds[tx].index = 1
ds[tx].type = "DERIVE"
ds[tx].minimal_heartbeat = 20
ds[tx].min = 0,0000000000e+00
ds[tx].max = NaN
ds[tx].last_ds = "73397119"
ds[tx].value = 9,1800000000e+01
ds[tx].unknown_sec = 0
rra[0].cf = "AVERAGE"
rra[0].rows = 1200

Here we have two variables: tx and rx

Graphs!

Let’s graph our stats. We start with some basic data: system load, memory usage and number of processes.

We create the script /var/www/html/stats/makegraphs.sh, and make it executable.

#!/bin/bash
BASERRD="/var/lib/collectd/rrd/localhost"

# system load:
# we want three lines for short / mid / long term load
rrdtool graph load.png \
        --lazy \
        --title="Load" \
        "DEF:s=$BASERRD/load/load.rrd:shortterm:AVERAGE" \
            "LINE1:s#1a5fb4:Short term" \
            "GPRINT:s:LAST:%5.2lf" \
        "DEF:m=$BASERRD/load/load.rrd:midterm:AVERAGE" \
            "LINE2:m#e5a50a:Mid term" \
            "GPRINT:m:LAST:%5.2lf" \
        "DEF:l=$BASERRD/load/load.rrd:longterm:AVERAGE" \
            "LINE1:l#9141ac:Long term" \
            "GPRINT:l:LAST:%5.2lf"

I will let you read the documentation for the juicy details of this command, but in short: we ask rrdtool to create a graph with titled “Load”, with data from load/load.rrd, first from variable shortterm. We then ask to draw it as a line, with color #1a5fb4 and label “Short term”. A legend entry will be added automatically. Then we write the last value registered for the variable, with a format string: %5.2lf will print the number with a width of 5 digits and a precision of 2. We repeat the code for the other variables, changing the color the label and the reference name (here s for short , m for mid and l for long). We ask rrdtool to save the result as load.png.

This is the result:

Colors are chosen from the GNOME HIG palette . Easy ๐Ÿ™‚

Next we draw the memory usage:

rrdtool graph memory.png \
        --lazy \
        --lower-limit 0 \
        --title="Memory" \
        "DEF:umem=$BASERRD/memory/memory-used.rrd:value:AVERAGE" \
            "AREA:umem#e01b24:Used" \
            "GPRINT:umem:LAST:%5.2lf%s" \
        "DEF:fmem=$BASERRD/memory/memory-free.rrd:value:AVERAGE" \
            "STACK:fmem#33d17a:Free" \
            "GPRINT:fmem:LAST:%5.2lf%s"

Basically it’s the same, here we draw used memory and free memory, whose data came from two different files, both with variable named value .

Two main differences here:

(I’m not plotting all the memory-related values, so the total area is not at 100%)

Last is a graph with processes. Which data to plot has been choose at random, I’m sure there are more useful things to know, but here it is:

rrdtool graph processes.png \
        --lazy \
        --title="Processes" \
        "DEF:r=$BASERRD/processes/ps_state-running.rrd:value:AVERAGE" \
            "LINE1:r#33d17a:Running" \
            "GPRINT:r:LAST:%5.2lf" \
        "DEF:s=$BASERRD/processes/ps_state-sleeping.rrd:value:AVERAGE" \
            "LINE2:s#3584e4:Sleeping" \
            "GPRINT:s:LAST:%5.2lf" \
        "DEF:z=$BASERRD/processes/ps_state-zombies.rrd:value:AVERAGE" \
            "LINE1:z#e01b24:Zombies" \
            "GPRINT:z:LAST:%5.2lf" 

This is just like the load graph, nothing too fancy.

We run the script from the folder, it generates the images. Then we add the images to the page.

For a clean look, I decided to put the graphs inside a <details> element, which is collapsed by default:

<details>
    <summary>stats</summary>
    <section>
        <header><h2>System</h2></header>
        <div>
            <img src="stats/load.png">
            <img src="stats/memory.png">
            <img src="stats/processes.png">
        </div>
    </section>
</details>

A bit of css will position the images nicely on the page.

With the same process I’ve added also graphs for disks space usage and network packets count.

The result is this page:

Nice!

With this we end the first part about my DIY homeserver dashboard.

Next up: auto-update the graphs and moar stats!!!1!


Footnotes

^0: Code is available, but I’m not linking it because it’s not really ready to be used