A lightweight system-monitoring dashboard for the Raspberry Pi. A single Go binary collects system metrics, serves a self-contained web dashboard, and exposes a versioned REST API for third-party integration (e.g. home automation systems like openHAB). Runs as a systemd service.
- CPU usage - overall and per-core percentage, with a trend sparkline
- CPU frequency & governor (REST API) - per-core clock speed and
active
cpufreqscaling governor - Load average - 1/5/15 minute values shown as gauges, scaled to CPU core count
- CPU temperature - auto-detected thermal zone, with an optional
vcgencmd-sourced GPU temperature if available - Memory & swap usage
- Filesystem usage - per mounted filesystem, pseudo filesystems (tmpfs, proc, overlay, ...) excluded by default
- Network throughput per interface (optional, can be disabled)
- System identity - kernel version, OS distribution, Raspberry Pi model
- Uptime - device clock and time since boot
- Available apt updates - count and package list, with a staleness indicator for the underlying apt cache
- Short metric history (default: last 60 minutes) for sparklines, periodically snapshotted to a compact binary file so it survives restarts and reboots - no database
- Threshold alerts - a server-side engine maps each poll against the
configured warn/critical thresholds into per-metric alert states with
debounced fired/cleared events, exposed via
GET /api/v1/alerts - Alert webhooks - optionally POST a JSON payload (with a per-webhook severity filter, custom body template, retry-with-backoff and basic rate-limiting) to one or more HTTP endpoints on each fired/cleared transition, so alerts reach Slack, Discord, Home Assistant, ntfy, etc. Delivery is fully off the collection path, so a slow webhook never blocks metric sampling
- Light/dark theme toggle - follows the OS setting by default, with a manual override remembered in the browser
- A versioned REST API (
/api/v1/...) for third-party consumers, with optional API-key authentication - seedocs/API.md
┌─────────────────────────────┐
│ pimonitor (Go) │
│ │
/proc, /sys ───▶ │ collector ──▶ httpapi │───▶ Web dashboard
apt cache │ (in-memory │ │ (embedded assets)
│ ring buffers) ▼ │
│ │ /api/v1/... │───▶ Third-party clients
│ │ alert events │ (e.g. openHAB)
│ ▼ │
│ notifier ─────────────────│───▶ HTTP webhooks
└─────────────────────────────┘ (Slack, ntfy, HA, ...)
pimonitor.service pimonitor-apt-update.timer (root)
runs unprivileged <-- refreshes apt cache every 6h;
reads /proc, /sys, pimonitor itself never needs
and the apt cache root privileges
read-only
The main service (pimonitor.service) never requires root: it only reads
world-readable files under /proc, /sys/class/thermal,
/etc/os-release, and the apt cache, plus the read-only
apt list --upgradable command. A separate, root-privileged systemd timer
(pimonitor-apt-update.timer) refreshes the apt cache periodically -
see SECURITY.md for the full threat model.
Requires Go 1.22+.
make build # native build, for local development -> bin/pimonitor
make build-arm64 # cross-compile for 64-bit Raspberry Pi OS (Pi 3/4/5)
make build-arm # cross-compile for 32-bit / Pi Zero/1 (GOARM=6)
make test # go test ./...
make lint # golangci-lint (also enforced in CI)make run starts the server locally against
packaging/pimonitor.example.yaml - useful for frontend/API development on
a non-Pi machine. Hardware-specific metrics (e.g. CPU temperature) simply
report as unavailable rather than failing.
Pre-built binaries for tagged releases are published via GitHub Actions using goreleaser - see the Releases page.
On the Pi, download the release tarball matching your Raspberry Pi OS and
extract it. Release assets are named
pimonitor_<version>_linux_<arch>.tar.gz, with <arch> being:
arm64- 64-bit Raspberry Pi OS (Pi 3/4/5)armv6- 32-bit Raspberry Pi OS, or any Pi Zero/1
The snippet below fetches the latest version automatically via the GitHub
API, so you only need to set ARCH:
ARCH=arm64 # or armv6, see above
VERSION=$(curl -fsSL https://api.github.com/repos/larslaskowski/pimonitor/releases/latest | grep -m1 '"tag_name"' | cut -d '"' -f4)
wget "https://github.com/larslaskowski/pimonitor/releases/download/${VERSION}/pimonitor_${VERSION#v}_linux_${ARCH}.tar.gz"
tar xzf "pimonitor_${VERSION#v}_linux_${ARCH}.tar.gz"
cd "pimonitor_${VERSION#v}_linux_${ARCH}"
ls
# install.sh pimonitor pimonitor.service pimonitor-apt-update.service
# pimonitor-apt-update.timer pimonitor.example.yaml README.md LICENSE.mdAlternatively, pick a specific version and architecture manually from the
Releases page. Either
way you get a single pimonitor_<version>_linux_<arch>/ directory containing
everything the installer needs — the binary (named plainly pimonitor),
install.sh, and the systemd units. Run the remaining steps from inside that
directory.
install.sh never overwrites an existing /etc/pimonitor/config.yaml -
it only writes one if none exists yet. If you want to start with
non-default settings (for example a different listen_addr because port
8080 is already used by something else on the Pi) instead of editing
the config and restarting afterwards, stage it yourself before running
install.sh:
sudo mkdir -p /etc/pimonitor
sudo cp pimonitor.example.yaml /etc/pimonitor/config.yaml
sudo nano /etc/pimonitor/config.yaml # e.g. change listen_addrThis matters in particular for listen_addr: if the configured port is
already in use, pimonitor.service fails to bind it and keeps
crash-looping (Restart=on-failure, retried every 5s) - but
install.sh itself reports success and prints no error, because
systemctl enable --now returns immediately without waiting to see
whether the service actually stays up. Always verify the service is
running after installing, see step 4.
sudo ./install.shRun it from inside the extracted directory (see step 1). The installer picks
up the pimonitor binary sitting next to it automatically — no path argument
needed.
This creates an unprivileged pimonitor system user, installs the binary
to /usr/local/bin/pimonitor, writes a default config to
/etc/pimonitor/config.yaml (if one doesn't already exist), installs the
two systemd units, and enables/starts both. Because the config file may
contain the api_key secret, the installer keeps it readable only by root
and the pimonitor group (/etc/pimonitor is 750 root:pimonitor, the
config file 640 root:pimonitor) — re-running install.sh also tightens
the permissions of an existing config without touching its content. See
packaging/pimonitor.example.yaml for
every configuration option.
If you are installing from a source checkout instead of a release archive
(so there is no pre-built binary next to the script) and have a Go toolchain
installed directly on the Pi, install.sh builds one for you:
sudo ./packaging/install.shsystemctl status pimonitor.service pimonitor-apt-update.timer
journalctl -u pimonitor -fA successful install shows active (running) for pimonitor.service.
If instead you see activating (auto-restart) or a growing restart
count, the process is crash-looping on startup (a busy listen_addr
port is the most common cause). Check the actual error with
journalctl -u pimonitor -n 50, fix /etc/pimonitor/config.yaml
accordingly, then apply it with:
sudo systemctl restart pimonitor.serviceThe dashboard is then available at http://<pi-address>:8080/ (or
whatever port you configured).
If you're replacing RPi-Monitor
with PiMonitor on the same device, remove RPi-Monitor completely before
installing PiMonitor. PiMonitor listens on
port 8080 by default, RPi-Monitor's bundled lighttpd on 8888, so the two
don't collide as long as you keep the default ports - but if you plan to
reuse RPi-Monitor's old port (e.g. set listen_addr to :8888 so existing
bookmarks/firewall rules keep working), RPi-Monitor's lighttpd must be
stopped first, or pimonitor.service will fail to bind that port and
crash-loop on startup (see step 4 of the installation instructions above).
Installing PiMonitor first and cleaning up RPi-Monitor afterwards works too,
but only if you're not reusing the port.
The exact paths below assume RPi-Monitor was installed from the .deb
package; adjust them if you built it manually. As with any rm -rf, take a
backup first if you're unsure and review each command before running it.
sudo systemctl stop rpimonitorStops the running service so its files aren't locked during removal.
sudo systemctl disable rpimonitorRemoves the systemd symlinks so RPi-Monitor no longer starts on boot.
sudo apt-get remove --purge rpimonitorUninstalls the binaries; --purge also removes the package's own
configuration files.
sudo apt-get autoremove --purgeRemoves libraries that were only pulled in for RPi-Monitor and any orphaned configuration files left behind.
RPi-Monitor leaves files outside of what apt manages:
sudo rm -rf /etc/rpimonitor/
sudo rm -rf /usr/share/rpimonitor/
sudo rm -rf /var/lib/rpimonitor/
sudo rm -rf /var/log/rpimonitor/
sudo rm -rf /var/www/rpimonitor/ # only if you had a web server integrationRPi-Monitor typically schedules its sensor-collection script via cron:
sudo crontab -eRemove any lines referencing rpimonitor or
/usr/share/rpimonitor/scripts/. Also check for a system-wide job and
remove it if present:
sudo rm -f /etc/cron.d/rpimonitorOnly relevant if RPi-Monitor was installed manually rather than via apt:
ls /etc/systemd/system/ | grep rpimonitor
sudo rm -f /etc/systemd/system/rpimonitor.service # if found
sudo systemctl daemon-reloadOnly relevant if RPi-Monitor was fronted by Apache or Nginx instead of its
bundled lighttpd:
# Apache
sudo rm -f /etc/apache2/conf-available/rpimonitor.conf
sudo a2disconf rpimonitor
sudo systemctl reload apache2
# Nginx
sudo rm -f /etc/nginx/sites-enabled/rpimonitor
sudo systemctl reload nginxwhich rpimonitor # no output
systemctl status rpimonitor # "Unit rpimonitor.service could not be found."
dpkg -l | grep rpimonitor # no outputIf all three come back clean, RPi-Monitor is fully removed and PiMonitor is the only monitoring service left running on the Pi.
PiMonitor is distributed as a single binary, so upgrading is a matter of
replacing that binary and restarting the service. install.sh is safe to
re-run for this purpose: it overwrites the binary and the systemd units but
leaves an existing /etc/pimonitor/config.yaml untouched.
-
Get the new version. Either download the binary (or release tarball) for your architecture from the Releases page, or pull the latest source and cross-compile it (
make build-arm64/make build-arm). -
Re-run the installer from inside the newly extracted release directory:
sudo ./install.sh
This replaces
/usr/local/bin/pimonitor, refreshes the systemd units, and runssystemctl daemon-reload. Your configuration is preserved. -
Restart the service so the running process picks up the new binary.
install.shstarts the units but does not restart an already-running service, so do it explicitly:sudo systemctl restart pimonitor.service
If the
pimonitor-apt-update.timeror its service unit changed, also runsudo systemctl restart pimonitor-apt-update.timer. -
Verify the new version is running:
pimonitor -version systemctl status pimonitor.service journalctl -u pimonitor -n 20
Because nothing is persisted across restarts (the in-memory history is rebuilt from scratch), the sparklines will simply start empty again after an update — there is no database to migrate.
New configuration options: upgrades never modify your existing
config.yaml. When a release adds settings, compare your file against the
current
packaging/pimonitor.example.yaml and
copy over any new keys you want to use. All settings have sensible defaults,
so a config from an older version keeps working unchanged.
REST API compatibility: the /api/v1/... contract is stable across
updates — a breaking change to an endpoint's JSON shape ships as
/api/v2/... instead, so existing integrations (e.g. openHAB) keep working
after an upgrade. See docs/API.md.
See docs/API.md for the full contract, including an
example openHAB HTTP Binding configuration. Quick example:
curl -s http://raspberrypi.local:8080/api/v1/metrics | jq '.cpu.overall_percent'- openHAB — ready-to-use HTTP-binding
.things/.itemswiring PiMonitor's metrics into openHAB, plus a Prometheus (json_exporter) alternative.
All settings have sensible defaults; override via /etc/pimonitor/config.yaml
(see packaging/pimonitor.example.yaml)
or CLI flags (-listen, -log-level, -api-key, -config, -version).
Flags take precedence over the config file, which takes precedence over
built-in defaults.
See CLAUDE.md for build/test conventions and project
structure notes. Contributions are welcome - see the issue and pull
request templates under .github/ for what to include.
