Command to run - Best practices

Best practices for "command to run

Many configuration actions in qbee allow you to invoke a "command to run" after a file is distributed or a process is restarted. This document shows you some best practices how to use this.

When files are being written on the system by the qbee agent, there are no execution bit set. This means that files are not executable by default. So if the files you have distributed are scripts, then best practice is to call the interpreter of the script (eg. bash, python etc.).

Executing scripts with command to run

The best practice is to call the interpreter of the script (eg. bash, python etc.)
As an example, if you distribute a bash script called /usr/local/bin/myscript.sh, then this would be executed with:

bash /usr/local/bin/myscript.sh

Some scripts and executables do not properly close stdin/stderr/stdout. So if you experience that execution of scripts hang then it might be necessary to explicitly close stdin/stderr/stdout. This is done as follows:

bash /usr/local/bin/myscript.sh > /dev/null < /dev/null 2>&1

Understanding stdin and stdout

Linux uses stdin/stderr/stdout to handle input, output and error information. Some scripts and executables do not properly close stdin/stderr/stdout. In order to prevent these from hanging they can be routed to /dev/null. This prevents the system from hanging, but then the qbee-agent will not be able to capture any output from the command execution. This command chain invokes the following parts:

> /dev/null close stdout

< /dev/null close stdin

2>&1 send stderr to stdout

Make sure your binaries are executable

Note that if you are calling a system binary or have indeed distributed your own binaries, you will need to make them executable by setting the executable bits. Eg. a binary built using golang called /usr/local/bin/my-golang-bin:

chmod 755 /usr/local/bin/my-golang-bin && /usr/local/bin/my-golang-bin

System binaries already installed on the system can be called directly as they are already set to be executable. Eg. restarting a the ssh service

systemctl restart sshd

Running commands as user

All commands are run as the administrative user (root). If you need to run your command as a different user, you need to use either sudo or su:

sudo -u myuser bash /usr/local/bin/myscript.sh

or

su -c 'bash /usr/local/bin/myscript.sh' -l myuser

Running commands in the background

If you need to run processes in the background using an & , the best practice is to do this from a script. Also, it is often required to close the stdin/stderr/stdout file descriptors:

Eg. /usr/local/bin/myscript.sh

    #!/usr/bin/env bash
    /usr/bin/my-background-process &

And then call it in qbee configuration as follows:

bash /usr/local/bin/myscript.sh > /dev/null < /dev/null 2>&1

Please be careful with chmod on templated files

Setting permissions with chmod on templated files currently doesn’t work as the permissions will be set to the same as the template on disk (which is -rw-r--r--). You should therefore avoid setting permissions on files produced by templates as this will be constantly be reverted to be consistent with the template, potentially causing an execution loop. Best practice is to read them into the main script. See an example here:

node-red-service.sh

#!/usr/bin/env bash

source /home/pi/node-red-service.conf

sudo systemctl $STATE nodered.service

This is including the templated config file with the source command. The node-red-service.conf.tmpl could look like this:

This script is uploaded to the qbee file manager with the name node-red-service.sh.

Then the template file is created and uploaded as node-red-service.tmpl.

node-red-service.tmpl

# node-red service template for node-red-service script
# run once trigger {{epoch}}

# use start,stop,restart, disable, enable
export STATE="{{service-state}}"