Sunday, 12 February 2017

Simple Web monitor/maintanence for RPi

Basic maintanence and monitoring for your RPi can obviously be achieved by ssh into the machine but sometimes there needs to be an easier way.

Most of my RPi units are headless and will run 24/7 - having to log in to the host for basic monitoring can be a real pain, requiring another host (typically a PC or laptop), just to check basic/simple status. Whilst requesting a simple shutdown or restart can be acheived with a phsical push button (normal-open momentary) switch paired with a python script it does mean that you actually need to obtain the push button.

I wanted another solution.

My basic requirements:
  • perform pre-defined suite of actions without another PC/via ssh
    • shutdown or restart machine
    • force restart of troublesome/intermittent services (like bluetooth wichi will consume CPU if a bluetooth keyboard suddenly disappears)
    • start/stop playing pre-defined web radio station
  • provide consolidated view of pre-defined system status (filesystem free space, current established connections, summary of top processes)
  • password protect access to the same maintanence system from outside of the home network/other side of firewall

Since all of my RPi units will be connected to my home network or at least reachable via some known IP or name the solution to these requirements can be provided through a simple webpage hosted on each RPi (using lighttpd as the webserver) and backed by a set of cgi scripts.

Basic lighttpd setup

A webserver is easily the most appropriate solution providing an interface to smart phones/tablets to interact with the host as well as bigger machines. The interface to the backend scripts can also be very simple (hitting a URL mapping to backend cgi script or it can be a more complicated webpage built using jquery/ajax/anjular/bootstrap technologies etc. Whilst this could be taken further with REST apis etc it is careful not to overengineer the solution and create a resource hogging server on the RPi side.

The basic configuration to enable http to https redirection as well as filtering based on remote (client side) IP.

# /etc/lighttpd/lighttpd.conf
server.modules = (

var.server_root      = "/var/www"
server.document-root = "/var/www/html"
server.upload-dirs   = ( "/var/cache/lighttpd/uploads" )
server.errorlog      = "/var/log/lighttpd/error.log"      = "/var/run/"
server.username      = "www-data"
server.groupname     = "www-data"
server.port          = 80

auth.backend      = "htdigest"
auth.backend.htdigest.userfile = "/etc/lighttpd/htdigest.user"

index-file.names     = ( "index.php", "index.html", "index.lighttpd.html" )
url.access-deny      = ( "~", ".inc" )
static-file.exclude-extensions = ( ".php", ".pl", ".fcgi" )

compress.cache-dir   = "/var/cache/lighttpd/compress/"
compress.filetype    = ( "application/javascript", "text/css", "text/html", "text/plain" )

# default listening port for IPv6 falls back to the IPv4 port
include_shell "/usr/share/lighttpd/ " + server.port
include_shell "/usr/share/lighttpd/"
include_shell "/usr/share/lighttpd/"

## enable https and generate cert
## openssl req -new -x509 \
##    -keyout /etc/lighttpd/certs/lighttpd.pem \
##    -out    /etc/lighttpd/certs/lighttpd.pem \
##    -days 3650 \
##    -nodes
$SERVER["socket"] == ":443" {
    ssl.engine   = "enable"
    ssl.pemfile  = "/etc/lighttpd/certs/lighttpd.pem"

## force all http -> https
$HTTP["scheme"] == "http" {
    # capture vhost name with regex conditiona -> %0 in redirect pattern
    # must be the most inner block to the redirect rule
    $HTTP["host"] =~ ".*" {
        url.redirect = (".*" => "https://%0$0")

## internal network is 192.168.0.x - promopt all external traffic for passwd
## should already have been routed to https
$HTTP["remoteip"] != "" {
    ssl.engine   = "enable"
    ssl.pemfile  = "/etc/lighttpd/certs/lighttpd.pem"

    server.errorlog = "/var/log/lighttpd/error-extnl.log"
    # accesslog.filename = "/var/log/lighttpd/access-extnl.log"
    auth.require = ( "/" => (
     "method"  => "digest",
     "realm"   => "realm",
     "require" => "valid-user"

lighttpd on Raspbian auto pulls in files in the conf-enabled folder:
# /etc/lighttpd/conf-enabled/10-cgi.conf
server.modules += ( "mod_cgi" )

cgi.assign = ( ".pl"  => "/usr/bin/perl",
               ".cgi" => "/usr/bin/perl",
               ".rb"  => "/usr/bin/ruby",
               ".erb" => "/usr/bin/eruby",
               ".py"  => "/usr/bin/python",
               ".sh"  => "/bin/bash" )

alias.url += ( "/cgi-bin" => server_root + "/cgi-bin" )
$HTTP["url"] =~ "^/cgi-bin" {
   cgi.assign = ( "" => "" )
Note that to enable correct handling of bash cgi scripts we must map .sh files to be started using /bin/bash explicitly via the cgi.assign - it appears that the #!/bin/bash at the top of your cgi scripts are ignored and /bin/sh is not a symlink to bash.

Additionally, with the alias.url directive, we can call cgi files with /cgi-bin/ for the files to be outside of the document_root

Choice of Frontend

The webpage frontend is built using bootstrap/js so it will should be suitable for small screen devices as well as larger counterparts and data requests/responses will be handled via ajax to remove the need for full webpage reloads to have informational sections on the webpage update.

The bulk of the webpage is to provide information sent back from the server to indicate the server status.

Letting cgi-bin work

The webserver will run as the www-data user so we will need to grant access to the relevant system commands via sudo:
# /etc/sudoers
www-data ALL=(ALL) NOPASSWD: /sbin/shutdown, /usr/bin/, /usr/bin/mocp, /bin/systemctl 
Again this means that anyone with access to your network will be able to run the cgi scripts although we've added a simple passwd requirement for anyone coming from outside of the network. However, if anyone is on our network it means they're probably close enough to come and rip the power out of the RPi anyway so it's not a great concern.

The files for webpage are available on github

No comments:

Post a Comment