• 3 min read
  • A few of my tech projects experience occasional hiccups and need to be soft-reset from my Linux host (e.g. Wi-Fi SSID routed through VPN, Windows gaming VM with hardware passthough). This was annoying as it meant having a machine nearby to SSH and execute a few simple commands — often just a systemctl restart foo. Fortunately, homebridge-cmdswitch2 can easily expose arbitrary commands as lights so I would be able to easily bounce the services via my phone.

    First, since Homebridge should be running as its own system user, we need to give it permissions to restart services (as root). We don’t want to grant services to all of /bin/systemctl, so a wrapper script will be placed at /usr/local/bin/serviceswitch to encapsulate the desired behavior. Grant the homebridge user permission to run it with sudo:

    cat  /etc/sudoers.d/homebridge-cmdswitch
    homebridge ALL = (root) NOPASSWD: /usr/local/bin/serviceswitch

    Next, let’s create that /usr/local/bin/serviceswitch script with service status, start and stop commands - using such a wrapper also has the benefit that complex checks consisting of several commands can be performed. Keep in mind these are now being run as root from Homebridge!

    #!/bin/sh
    
    if [ "$(id -u)" -ne 0 ];then
      echo "You must run this script as root."
      exit 1
    fi
    
    usage() {
      error="$1"
      if [ ! -z "$error" ];then
        echo "Error: $error"
      fi
      echo "Usage: $0 [action] [service]"
    }
    
    action="$1"
    service="$2"
    if [ -z "$action" ] || [ -z "$service" ];then
      usage
      exit 1
    fi
    
    case $action in
      start|stop|status) ;;
      *) usage "invalid action, must be one of [start, stop, status]"; exit 1;;
    esac
    
    case $service in
      vm-guests)
        [ "$action" == "start" ] && (systemctl start libvirt-guests)
        [ "$action" == "stop" ] && (systemctl stop libvirt-guests)
        [ "$action" == "status" ] && { systemctl -q is-active libvirt-guests; exit $?; }
        ;;
      fileserver)
        [ "$action" == "start" ] && (systemctl start smb;systemctl start nmb;systemctl start netatalk)
        [ "$action" == "stop" ] && (systemctl stop smb;systemctl stop nmb;systemctl stop netatalk)
        [ "$action" == "status" ] && { (systemctl -q is-active smb && systemctl -q is-active nmb && systemctl -q is-active netatalk); exit $?; }
        ;;
      web)
        [ "$action" == "start" ] && (systemctl start httpd)
        [ "$action" == "stop" ] && (systemctl stop httpd)
        [ "$action" == "status" ] && { systemctl -q is-active httpd; exit $?; }
        ;;
      *) usage "invalid service"; exit 1;;
    esac
    exit 0

    Finally, here is the relevant platform section from the homebridge config:

    {
      "platforms": [{
        "platform": "cmdSwitch2",
        "name": "Command Switch",
        "switches": [{
           "name" : "vm-guests",
            "on_cmd": "sudo /usr/local/bin/serviceswitch start vm-guests",
            "off_cmd": "sudo /usr/local/bin/serviceswitch stop vm-guests",
            "state_cmd": "sudo /usr/local/bin/serviceswitch status vm-guests",
            "polling": false,
            "interval": 5,
            "timeout": 10000
        },
        {
           "name" : "fileserver",
            "on_cmd": "sudo /usr/local/bin/serviceswitch start fileserver",
            "off_cmd": "sudo /usr/local/bin/serviceswitch stop fileserver",
            "state_cmd": "sudo /usr/local/bin/serviceswitch status fileserver",
            "polling": false,
            "interval": 5,
            "timeout": 10000
        },
        {
           "name" : "web",
            "on_cmd": "sudo /usr/local/bin/serviceswitch start web",
            "off_cmd": "sudo /usr/local/bin/serviceswitch stop web",
            "state_cmd": "sudo /usr/local/bin/serviceswitch status web",
            "polling": false,
            "interval": 5,
            "timeout": 10000
        }]
      }]
    }