Automated headless Node-Red installation

If you want to use Node-Red in production you need to find out how to remotely deploy, update and manage Node-Red. Preferably you want to do this headless and as simple as possible. In this tutorial we install Node-Red headless on a Raspberry Pi. This can be done on other debian based embedded Linux devices in a similar way.

Node-Red should be installed through a script. Do not use the pre-build package!

The folks over at nodered.org strongly advice to install and upgrade Node-Red through a script. We conclude that they probably have good reasons for that and use a slightly changed version of their script for this.

We use the original script except for that we remove the interactive part and make the list of node modules accessible through the qbee UI. The script will:

  • remove the pre-packaged version of Node-RED and Node.js if they are present
  • install the current Node.js LTS release using the NodeSource. If it detects Node.js is already installed from NodeSource, it will ensure it is at least Node 8, but otherwise leave it alone
  • install the latest version of Node-RED using npm
  • allow to install a collection of useful Pi-specific nodes through the qbee UI
  • setup Node-RED to run as a service and provide a set of commands to work with the service

This is all done through qbee "Configure -> File distribution". We need to create and upload two files. The first is the installation script. This is more or less the original script without interaction and the modules to install are exposed to qbee. This is done by sourcing variables from the node-red-install.conf source /home/pi/node-red-install-config.conf and later introducing the variable in line 148 as EXTRANODES="$EXTRANODES".

#!/bin/bash
#
# Copyright 2016,2020 JS Foundation and other contributors, https://js.foundation/
# Copyright 2015,2016 IBM Corp.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# Node-RED Installer for DEB based systems



source /home/pi/node-red-install-config.conf

umask 0022
tgta=12.18.3   # need arm6l latest from https://unofficial-builds.nodejs.org/download/release/
tgtl=12.16.3   # need x86 latest from https://unofficial-builds.nodejs.org/download/release/

usage() {
  cat << EOL
Usage: $0 [options]

Options:
  --help            display this help and exits
  --confirm-root    install as root without asking confirmation
  --confirm-install confirm installation without asking confirmation
  --confirm-pi      confirm installation of PI specific nodes without asking confirmation
EOL
}

if [ $# -gt 0 ]; then
  # Parsing parameters
  while (( "$#" )); do
    case "$1" in
      --help)
        usage && exit 0
        shift
        ;;
      --confirm-root)
        CONFIRM_ROOT="y"
        shift
        ;;
      --confirm-install)
        CONFIRM_INSTALL="y"
        shift
        ;;
      --confirm-pi)
        CONFIRM_PI="y"
        shift
        ;;
      --) # end argument parsing
        shift
        break
        ;;
      -*|--*=) # unsupported flags
        echo "Error: Unsupported flag $1" >&2
        exit 1
        ;;
    esac
  done
fi

echo -ne "\033[2 q"
if [[ -e /mnt/dietpi_userdata ]]; then
    echo -ne "\n\033[1;32mDiet-Pi\033[0m detected - only going to add the  \033[0;36mnode-red-start, -stop, -log\033[0m  commands.\n"
    echo -ne "Flow files and other things worth backing up can be found in the \033[0;36m/mnt/dietpi_userdata/node-red\033[0m directory.\n\n"
    echo -ne "Use the  \033[0;36mdietpi-software\033[0m  command to un-install and re-install \033[38;5;88mNode-RED\033[0m.\n"
    echo "journalctl -f -n 25 -u node-red -o cat" > /usr/bin/node-red-log
    chmod +x /usr/bin/node-red-log
    echo "dietpi-services stop node-red" > /usr/bin/node-red-stop
    chmod +x /usr/bin/node-red-stop
    echo "dietpi-services start node-red" > /usr/bin/node-red-start
    echo "journalctl -f -n 0 -u node-red -o cat" >> /usr/bin/node-red-start
    chmod +x /usr/bin/node-red-start
else


if [[ "$(uname)" != "Darwin" ]]; then
# if curl -f https://www.npmjs.com/package/node-red  >/dev/null 2>&1; then
if curl -I https://registry.npmjs.org/@node-red/util  >/dev/null 2>&1; then
echo -e '\033]2;'Node-RED update'\007'
echo " "
echo "This script will remove versions of Node.js prior to version 7.x, and Node-RED and"
echo "if necessary replace them with Node.js 12.x LTS (erbium) and the latest Node-RED from Npm."
echo " "
echo "It also moves any Node-RED nodes that are globally installed into your user"
echo "~/.node-red/node_modules directory, and adds them to your package.json, so that"
echo "you can manage them with the palette manager."
echo " "
echo "It also tries to run 'npm rebuild' to refresh any extra nodes you have installed"
echo "that may have a native binary component. While this normally works ok, you need"
echo "to check that it succeeds for your combination of installed nodes."
echo " "
echo "To do all this it runs commands as root - please satisfy yourself that this will"
echo "not damage your Pi, or otherwise compromise your configuration."
echo "If in doubt please backup your SD card first."
echo " "
if [[ -e $HOME/.nvm ]]; then
    echo -ne '\033[1mNOTE:\033[0m We notice you are using \033[38;5;88mnvm\033[0m. Please ensure it is running the current LTS version.\n'
    echo -ne 'Using nvm is NOT RECOMMENDED. Node-RED will not run as a service under nvm.\r\n\n'
fi

yn="y"
[ ! "${yn}" ] && read -p "Are you really sure you want to do this ? [y/N] ? " yn
case $yn in
    [Yy]* )

        # Here qbee.io allows to set extra nodes with a space in between
        echo ""

        # Here qbee.io allows to set extra nodes with a space in between them. 
        response="y"
        [ ! "${response}" ] && read -r -t 15 -p "Would you like to install the Pi-specific nodes ? [y/N] ? " response
        if [[ "$response" =~ ^([yY])+$ ]]; then
            EXTRANODES="$EXTRANODES"
            EXTRAW="install"
        fi

        # this script assumes that $HOME is the folder of the user that runs node-red
        # that $USER is the user name and the group name to use when running is the
        # primary group of that user
        # if this is not correct then edit the lines below
        MYOS=$(cat /etc/*release | grep "^ID=" | cut -d = -f 2)
        NODERED_HOME=$HOME
        NODERED_USER=$USER
        NODERED_GROUP=`id -gn`
        GLOBAL="true"
        TICK='\033[1;32m\u2714\033[0m'
        CROSS='\033[1;31m\u2718\033[0m'
        cd "$NODERED_HOME" || exit 1
        clear
        echo -e "\nRunning Node-RED $EXTRAW for user $USER at $HOME on $MYOS\n"
        time1=$(date)
        echo "" | sudo tee -a /var/log/nodered-install.log >>/dev/null
        echo "***************************************" | sudo tee -a /var/log/nodered-install.log >>/dev/null
        echo "" | sudo tee -a /var/log/nodered-install.log >>/dev/null
        echo "Started : "$time1 | sudo tee -a /var/log/nodered-install.log >>/dev/null
        echo "Running for user $USER at $HOME" | sudo tee -a /var/log/nodered-install.log >>/dev/null
        echo -ne '\r\nThis can take 20-30 minutes on the slower Pi versions - please wait.\r\n\n'
        echo -ne '  Stop Node-RED                       \r\n'
        echo -ne '  Remove old version of Node-RED      \r\n'
        echo -ne '  Remove old version of Node.js       \r\n'
        echo -ne '  Install Node.js                     \r\n'
        echo -ne '  Clean npm cache                     \r\n'
        echo -ne '  Install Node-RED core               \r\n'
        echo -ne '  Move global nodes to local          \r\n'
        echo -ne '  Install extra Pi nodes              \r\n'
        echo -ne '  Npm rebuild existing nodes          \r\n'
        echo -ne '  Add shortcut commands               \r\n'
        echo -ne '  Update systemd script               \r\n'
        echo -ne '                                      \r\n'
        echo -ne '\r\nAny errors will be logged to   /var/log/nodered-install.log\r\n'
        echo -ne '\033[14A'

        # stop any running node-red service
        if sudo service nodered stop 2>&1 | sudo tee -a /var/log/nodered-install.log >>/dev/null ; then CHAR=$TICK; else CHAR=$CROSS; fi
        echo -ne "  Stop Node-RED                       $CHAR\r\n"

        # save any global nodes
        GLOBALNODES=$(find /usr/local/lib/node_modules/node-red-* -maxdepth 0 -type d -printf '%f\n' 2>/dev/null)
        GLOBALNODES="$GLOBALNODES $(find /usr/lib/node_modules/node-red-* -maxdepth 0 -type d -printf '%f\n' 2>/dev/null)"
        echo "Found global nodes: $GLOBALNODES :" | sudo tee -a /var/log/nodered-install.log >>/dev/null

        # remove any old node-red installs or files
        sudo apt remove -y nodered 2>&1 | sudo tee -a /var/log/nodered-install.log >>/dev/null
        # sudo apt remove -y node-red-update 2>&1 | sudo tee -a /var/log/nodered-install.log >>/dev/null
        sudo rm -rf /usr/local/lib/node_modules/node-red* /usr/local/lib/node_modules/npm /usr/local/bin/node-red* /usr/local/bin/node /usr/local/bin/npm 2>&1 | sudo tee -a /var/log/nodered-install.log >>/dev/null
        sudo rm -rf /usr/lib/node_modules/node-red* /usr/bin/node-red* 2>&1 | sudo tee -a /var/log/nodered-install.log >>/dev/null
        echo -ne '  Remove old version of Node-RED      \033[1;32m\u2714\033[0m\r\n'

        nv="v0"
        nv2=""
        ndeb=$(apt-cache policy nodejs | grep Installed | awk '{print $2}')
        if [[ -x "$(command -v node)" ]]; then
            nv=`node -v | cut -d "." -f1`
            nv2=`node -v`
            # nv2=`apt list nodejs 2>/dev/null | grep dfsg | cut -d ' ' -f 2 | cut -d '-' -f 1`
            echo "Already have nodejs $nv2" | sudo tee -a /var/log/nodered-install.log >>/dev/null
        fi
        # ensure ~/.config dir is owned by the user
        echo "Now install nodejs" | sudo tee -a /var/log/nodered-install.log >>/dev/null
        sudo chown -Rf $NODERED_USER:$NODERED_GROUP $NODERED_HOME/.config/
        # maybe remove Node.js - or upgrade if nodesoure.list exists
        if [[ "$(uname -m)" =~ "i686" ]]; then
            echo "Using i686" | sudo tee -a /var/log/nodered-install.log >>/dev/null
            curl -sL -o /tmp/node.tgz https://unofficial-builds.nodejs.org/download/release/v$tgtl/node-v$tgtl-linux-x86.tar.gz 2>&1 | sudo tee -a /var/log/nodered-install.log >>/dev/null
            # unpack it into the correct places
            hd=$(head -c 9 /tmp/node.tgz)
            if [ "$hd" == "<!DOCTYPE" ]; then
                CHAR="$CROSS File $f not downloaded";
            else
                if sudo tar -zxf /tmp/node.tgz --strip-components=1 -C /usr 2>&1 | sudo tee -a /var/log/nodered-install.log >>/dev/null; then CHAR=$TICK; else CHAR=$CROSS; fi
            fi
            rm /tmp/node.tgz 2>&1 | sudo tee -a /var/log/nodered-install.log >>/dev/null
            echo -ne "  Install Node.js for i686            $CHAR"
        elif uname -m | grep -q armv6l ; then
            sudo apt remove -y nodejs nodejs-legacy npm 2>&1 | sudo tee -a /var/log/nodered-install.log >>/dev/null
            sudo rm -rf /etc/apt/sources.d/nodesource.list /usr/lib/node_modules/npm*
            echo -ne "  Remove old version of Node.js       $TICK\r\n"
            echo -ne "  Install Node.js for Armv6           \r"
            # f=$(curl -sL https://nodejs.org/download/release/latest-dubnium/ | grep "armv6l.tar.gz" | cut -d '"' -f 2)
            # curl -sL -o node.tgz https://nodejs.org/download/release/latest-dubnium/$f 2>&1 | sudo tee -a /var/log/nodered-install.log >>/dev/null
            curl -sL -o /tmp/node.tgz https://unofficial-builds.nodejs.org/download/release/v$tgta/node-v$tgta-linux-armv6l.tar.gz 2>&1 | sudo tee -a /var/log/nodered-install.log >>/dev/null
            # unpack it into the correct places
            hd=$(head -c 9 /tmp/node.tgz)
            if [ "$hd" == "<!DOCTYPE" ]; then
                CHAR="$CROSS File $f not downloaded";
            else
                if sudo tar -zxf /tmp/node.tgz --strip-components=1 -C /usr 2>&1 | sudo tee -a /var/log/nodered-install.log >>/dev/null; then CHAR=$TICK; else CHAR=$CROSS; fi
            fi
            # remove the tgz file to save space
            rm /tmp/node.tgz 2>&1 | sudo tee -a /var/log/nodered-install.log >>/dev/null
            echo -ne "  Install Node.js for Armv6           $CHAR"
        elif [[ -e $HOME/.nvm ]]; then
            echo -ne '  Using NVM to manage Node.js         +   please run   \033[0;36mnvm use lts\033[0m   before running Node-RED\r\n'
            echo -ne '  NOTE: Using nvm is NOT RECOMMENDED.     Node-RED will not run as a service under nvm.\r\n'
            export NVM_DIR=$HOME/.nvm
            [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"  # This loads nvm
            echo "Using NVM !!! $(nvm current)" 2>&1 | sudo tee -a /var/log/nodered-install.log >>/dev/null
            nvm install --lts --no-progress --latest-npm >/dev/null 2>&1
            nvm use lts >/dev/null 2>&1
            nvm alias default lts >/dev/null 2>&1
            echo "Now using --- $(nvm current)" 2>&1 | sudo tee -a /var/log/nodered-install.log >>/dev/null
            GLOBAL="false"
            ln -f -s $HOME/.nvm/versions/node/$(nvm current)/lib/node_modules/node-red/red.js  $NODERED_HOME/node-red
            echo -ne "  Update Node.js LTS                  $CHAR"
        elif [[ $(which n) ]]; then
            echo "Using n" | sudo tee -a /var/log/nodered-install.log >>/dev/null
            echo -ne "  Using N to manage Node.js           +\r\n"
            if sudo n lts 2>&1 | sudo tee -a /var/log/nodered-install.log >>/dev/null; then CHAR=$TICK; else CHAR=$CROSS; fi
            echo -ne "  Update Node.js LTS                  $CHAR"
        elif [ "$nv" = "v0" ] || [ "$nv" = "v1" ] || [ "$nv" = "v3" ] || [ "$nv" = "v4" ] || [ "$nv" = "v5" ] || [ "$nv" = "v6" ] || [ "$nv" = "v7" ] || [ "$nv" = "v9" ] || [ "$nv" = "v11" ] || [[ "$ndeb" =~ "dfsg" ]]; then
            echo "Updating nodejs $nv2" | sudo tee -a /var/log/nodered-install.log >>/dev/null
            if [[ -f /etc/apt/sources.list.d/nodesource.list ]]; then
                echo "Using nodesource.list" | sudo tee -a /var/log/nodered-install.log >>/dev/null
                if [ "$nv" = "v0" ] || [ "$nv" = "v1" ] || [ "$nv" = "v3" ] || [ "$nv" = "v4" ] || [ "$nv" = "v5" ] || [ "$nv" = "v6" ] || [ "$nv" = "v7" ] || [ "$nv" = "v9" ] || [ "$nv" = "v11" ] || [[ "$ndeb" =~ "dfsg" ]]; then
                    echo "Removing nodejs "$nv | sudo tee -a /var/log/nodered-install.log >>/dev/null
                    sudo apt remove -y nodejs nodejs-legacy npm 2>&1 | sudo tee -a /var/log/nodered-install.log >>/dev/null
                    sudo rm -rf /etc/apt/sources.d/nodesource.list /usr/lib/node_modules/npm*
                    OV=`cat /etc/os-release | grep VERSION_ID | cut -d '"' -f 2`
                    if [[ "$OV" = "8" ]]; then
                        if curl -sL https://deb.nodesource.com/setup_10.x | sudo -E bash - 2>&1 | sudo tee -a /var/log/nodered-install.log >>/dev/null; then CHAR=$TICK; else CHAR=$CROSS; fi
                    else
                        if curl -sL https://deb.nodesource.com/setup_12.x | sudo -E bash - 2>&1 | sudo tee -a /var/log/nodered-install.log >>/dev/null; then CHAR=$TICK; else CHAR=$CROSS; fi
                    fi
                else
                    CHAR="-"
                fi
                echo -ne "  Remove old version of Node.js       $CHAR  $nv\r\n"
                echo -ne "  Update Node.js LTS                  \r"
                if sudo apt install -y nodejs 2>&1 | sudo tee -a /var/log/nodered-install.log >>/dev/null; then CHAR=$TICK; else CHAR=$CROSS; fi
                echo -ne "  Update Node.js LTS                  $CHAR"
            else
                # clean out old nodejs stuff
                echo "Not using nodesource.list" | sudo tee -a /var/log/nodered-install.log >>/dev/null
                npv=$(npm -v 2>/dev/null | head -n 1 | cut -d "." -f1)
                sudo apt remove -y nodejs nodejs-legacy npm 2>&1 | sudo tee -a /var/log/nodered-install.log >>/dev/null
                sudo dpkg -r nodejs 2>&1 | sudo tee -a /var/log/nodered-install.log >>/dev/null
                sudo dpkg -r node 2>&1 | sudo tee -a /var/log/nodered-install.log >>/dev/null
                sudo rm -rf /opt/nodejs 2>&1 | sudo tee -a /var/log/nodered-install.log >>/dev/null
                sudo rm -f /usr/local/bin/node* 2>&1 | sudo tee -a /var/log/nodered-install.log >>/dev/null
                sudo rm -rf /usr/local/bin/npm* /usr/local/bin/npx* /usr/lib/node_modules/npm* 2>&1 | sudo tee -a /var/log/nodered-install.log >>/dev/null
                if [ "$npv" = "1" ]; then
                    sudo rm -rf /usr/local/lib/node_modules/node-red* /usr/lib/node_modules/node-red* 2>&1 | sudo tee -a /var/log/nodered-install.log >>/dev/null
                fi
                sudo apt -y autoremove 2>&1 | sudo tee -a /var/log/nodered-install.log >>/dev/null
                echo -ne "  Remove old version of Node.js       \033[1;32m\u2714\033[0m\r\n"
                echo "Grab the LTS bundle" | sudo tee -a /var/log/nodered-install.log >>/dev/null
                # sudo apt update -y 2>&1 | sudo tee -a /var/log/nodered-install.log >>/dev/null
                if [[ $(lsb_release -d) == *"18.10"* ]]; then
                    echo -ne "  Apt install Node.js                 \r"
                    if sudo apt install -y nodejs npm curl 2>&1 | sudo tee -a /var/log/nodered-install.log >>/dev/null; then CHAR=$TICK; else CHAR=$CROSS; fi
                    echo -ne "  Apt install Node.js                 $CHAR"
                else
                    echo -ne "  Install Node.js LTS                 \r"
                    # use the official script to install for other debian platforms
                    sudo apt install -y curl 2>&1 | sudo tee -a /var/log/nodered-install.log >>/dev/null
                    OV=`cat /etc/os-release | grep VERSION_ID | cut -d '"' -f 2`
                    if [[ "$OV" = "8" ]]; then
                        curl -sL https://deb.nodesource.com/setup_10.x | sudo -E bash - 2>&1 | sudo tee -a /var/log/nodered-install.log >>/dev/null
                    else
                        curl -sL https://deb.nodesource.com/setup_12.x | sudo -E bash - 2>&1 | sudo tee -a /var/log/nodered-install.log >>/dev/null
                    fi
                    if sudo apt install -y nodejs 2>&1 | sudo tee -a /var/log/nodered-install.log >>/dev/null; then CHAR=$TICK; else CHAR=$CROSS; fi
                    echo -ne "  Install Node.js LTS                 $CHAR"
                fi
                sudo npm i -g --no-progress --no-update-notifier --no-audit --no-fund npm 2>&1 | sudo tee -a /var/log/nodered-install.log >>/dev/null;
            fi
        else
            CHAR="-"
            echo -ne "  Remove old version of Node.js       $CHAR\n"
            echo -ne "  Leave existing Node.js              $CHAR"
        fi
        NUPG=$CHAR
        hash -r
        rc=""
        if nov=$(node -v 2>/dev/null); then :; else rc="ERR"; fi
        if npv=$(npm -v 2>/dev/null); then :; else rc="ERR"; fi
        echo "Versions: node:$nov npm:$npv" | sudo tee -a /var/log/nodered-install.log >>/dev/null
        if [[ $rc == "" ]]; then
            echo -ne "   Node $nov   Npm $npv\r\n"
        else
            echo -ne "\b$CROSS   Failed to install Node.js - Exit\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n"
            exit 2
        fi
        if [ "$EUID" == "0" ]; then npm config set unsafe-perm true &>/dev/null; fi

        # clean up the npm cache and node-gyp
        if [[ $NUPG == $TICK ]]; then
            if [[ $GLOBAL == "true" ]]; then
                sudo npm cache clean --force 2>&1 | sudo tee -a /var/log/nodered-install.log >>/dev/null
            else
                npm cache clean --force 2>&1 | sudo tee -a /var/log/nodered-install.log >>/dev/null
            fi
            if sudo rm -rf "$NODERED_HOME/.node-gyp" "$NODERED_HOME/.npm" /root/.node-gyp /root/.npm; then CHAR=$TICK; else CHAR=$CROSS; fi
        fi
        echo -ne "  Clean npm cache                     $CHAR\r\n"

        # and install Node-RED
        echo "Now install Node-RED" | sudo tee -a /var/log/nodered-install.log >>/dev/null
        if [[ $GLOBAL == "true" ]]; then
            if sudo npm i -g --unsafe-perm --no-progress --no-update-notifier --no-audit --no-fund node-red@latest 2>&1 | sudo tee -a /var/log/nodered-install.log >>/dev/null; then CHAR=$TICK; else CHAR=$CROSS; fi
        else
            if npm i -g --unsafe-perm --no-progress --no-update-notifier --no-audit --no-fund node-red@latest 2>&1 | sudo tee -a /var/log/nodered-install.log >>/dev/null; then CHAR=$TICK; else CHAR=$CROSS; fi
        fi
        nrv=$(npm --no-progress --no-update-notifier --no-audit --no-fund -g ls node-red | grep node-red | cut -d '@' -f 2 | sudo tee -a /var/log/nodered-install.log) >>/dev/null 2>&1
        echo -ne "  Install Node-RED core               $CHAR   $nrv\r\n"

        # install any nodes, that were installed globally, as local instead
        echo "Now create basic package.json for the user and move any global nodes" | sudo tee -a /var/log/nodered-install.log >>/dev/null
        mkdir -p "$NODERED_HOME/.node-red/node_modules"
        sudo chown -Rf $NODERED_USER:$NODERED_GROUP $NODERED_HOME/.node-red/ 2>&1 >>/dev/null
        pushd "$NODERED_HOME/.node-red" 2>&1 >>/dev/null
            npm config set update-notifier false 2>&1 >>/dev/null
            if [ ! -f "package.json" ]; then
                echo '{' > package.json
                echo '  "name": "node-red-project",' >> package.json
                echo '  "description": "A Node-RED Project",' >> package.json
                echo '  "version": "0.0.1",' >> package.json
                echo '  "dependencies": {' >> package.json
                echo '  }' >> package.json
                echo '}' >> package.json
            fi
            CHAR="-"
            if [[ $GLOBALNODES != " " ]]; then
                if npm i --unsafe-perm --save --no-progress --no-update-notifier --no-audit --no-fund $GLOBALNODES 2>&1 | sudo tee -a /var/log/nodered-install.log >>/dev/null; then CHAR=$TICK; else CHAR=$CROSS; fi
            fi
            echo -ne "  Move global nodes to local          $CHAR\r\n"

            CHAR="-"
            if [[ ! -z $EXTRANODES ]]; then
                echo "Installing extra nodes: $EXTRANODES :" | sudo tee -a /var/log/nodered-install.log >>/dev/null
                if npm i --unsafe-perm --save --no-progress --no-update-notifier --no-audit --no-fund $EXTRANODES 2>&1 | sudo tee -a /var/log/nodered-install.log >>/dev/null; then CHAR=$TICK; else CHAR=$CROSS; fi
            fi
            echo -ne "  Install extra Pi nodes              $CHAR\r\n"

            # try to rebuild any already installed nodes
            if [[ $NUPG == $TICK ]]; then
                if npm rebuild --unsafe-perm 2>&1 | sudo tee -a /var/log/nodered-install.log >>/dev/null; then CHAR=$TICK; else CHAR=$CROSS; fi
                echo -ne "  Npm rebuild existing nodes          $CHAR\r\n"
            else
                echo -ne "  Npm rebuild existing nodes          -\r\n"
            fi
        popd 2>&1 >>/dev/null
        sudo chown -Rf $NODERED_USER:$NODERED_GROUP $NODERED_HOME/.npm 2>&1 >>/dev/null

        # add the shortcut and start/stop/log scripts to the menu
        echo "Now add the shortcut and start/stop/log scripts to the menu" | sudo tee -a /var/log/nodered-install.log >>/dev/null
        sudo mkdir -p /usr/bin
        if curl -f https://raw.githubusercontent.com/node-red/linux-installers/master/resources/node-red-icon.svg >/dev/null 2>&1; then
            sudo curl -sL -o /usr/share/icons/hicolor/scalable/apps/node-red-icon.svg https://raw.githubusercontent.com/node-red/linux-installers/master/resources/node-red-icon.svg 2>&1 | sudo tee -a /var/log/nodered-install.log >>/dev/null
            sudo curl -sL -o /usr/share/applications/Node-RED.desktop https://raw.githubusercontent.com/node-red/linux-installers/master/resources/Node-RED.desktop 2>&1 | sudo tee -a /var/log/nodered-install.log >>/dev/null
            sudo curl -sL -o /usr/bin/node-red-start https://raw.githubusercontent.com/node-red/linux-installers/master/resources/node-red-start 2>&1 | sudo tee -a /var/log/nodered-install.log >>/dev/null
            sudo curl -sL -o /usr/bin/node-red-stop https://raw.githubusercontent.com/node-red/linux-installers/master/resources/node-red-stop 2>&1 | sudo tee -a /var/log/nodered-install.log >>/dev/null
            sudo curl -sL -o /usr/bin/node-red-restart https://raw.githubusercontent.com/node-red/linux-installers/master/resources/node-red-restart 2>&1 | sudo tee -a /var/log/nodered-install.log >>/dev/null
            sudo curl -sL -o /usr/bin/node-red-reload https://raw.githubusercontent.com/node-red/linux-installers/master/resources/node-red-reload 2>&1 | sudo tee -a /var/log/nodered-install.log >>/dev/null
            sudo curl -sL -o /usr/bin/node-red-log https://raw.githubusercontent.com/node-red/linux-installers/master/resources/node-red-log 2>&1 | sudo tee -a /var/log/nodered-install.log >>/dev/null
            sudo curl -sL -o /etc/logrotate.d/nodered https://raw.githubusercontent.com/node-red/linux-installers/master/resources/nodered.rotate 2>&1 | sudo tee -a /var/log/nodered-install.log >>/dev/null
            sudo chmod +x /usr/bin/node-red-start
            sudo chmod +x /usr/bin/node-red-stop
            sudo chmod +x /usr/bin/node-red-restart
            sudo chmod +x /usr/bin/node-red-reload
            sudo chmod +x /usr/bin/node-red-log
            echo -ne "  Add shortcut commands               $TICK\r\n"
        else
            echo -ne "  Add shortcut commands               $CROSS\r\n"
        fi

        # add systemd script and configure it for $USER
        echo "Now add systemd script and configure it for $USER" | sudo tee -a /var/log/nodered-install.log >>/dev/null
        if sudo curl -sL -o /lib/systemd/system/nodered.service https://raw.githubusercontent.com/node-red/linux-installers/master/resources/nodered.service 2>&1 | sudo tee -a /var/log/nodered-install.log >>/dev/null; then CHAR=$TICK; else CHAR=$CROSS; fi
        # set the memory, User Group and WorkingDirectory in nodered.service
        if [ $(cat /proc/meminfo | grep MemTotal | cut -d ":" -f 2 | cut -d "k" -f 1 | xargs) -lt 895000 ]; then mem="256"; else mem="512"; fi
        sudo sed -i 's#=512#='$mem'#;' /lib/systemd/system/nodered.service
        sudo sed -i 's#^User=pi#User='$NODERED_USER'#;s#^Group=pi#Group='$NODERED_GROUP'#;s#^WorkingDirectory=/home/pi#WorkingDirectory='$NODERED_HOME'#;' /lib/systemd/system/nodered.service
        sudo systemctl daemon-reload 2>&1 | sudo tee -a /var/log/nodered-install.log >>/dev/null
        echo -ne "  Update systemd script               $CHAR\r\n"

        # remove unneeded large sentiment library to save space and load time
        sudo rm -f /usr/lib/node_modules/node-red/node_modules/multilang-sentiment/build/output/build-all.json 2>&1 | sudo tee -a /var/log/nodered-install.log >>/dev/null
        # on LXDE add launcher to top bar, refresh desktop menu
        file=/home/$NODERED_USER/.config/lxpanel/LXDE-pi/panels/panel
        if [ -e $file ]; then
            if ! grep -q "Node-RED" $file; then
                mat="lxterminal.desktop"
                ins="lxterminal.desktop\n    }\n    Button {\n      id=Node-RED.desktop"
                sudo sed -i "s|$mat|$ins|" $file 2>&1 | sudo tee -a /var/log/nodered-install.log >>/dev/null
                if xhost >& /dev/null ; then
                    export DISPLAY=:0 && lxpanelctl restart 2>&1 | sudo tee -a /var/log/nodered-install.log >>/dev/null
                fi
            fi
        fi

        # on Pi, add launcher to top bar, add cpu temp example, make sure ping works
        echo "Now add launcher to top bar, add cpu temp example, make sure ping works" | sudo tee -a /var/log/nodered-install.log >>/dev/null
        if sudo grep -q BCM2 /proc/cpuinfo; then
            sudo setcap cap_net_raw+eip $(eval readlink -f `which node`) 2>&1 | sudo tee -a /var/log/nodered-install.log >>/dev/null
            sudo setcap cap_net_raw=ep /bin/ping 2>&1 | sudo tee -a /var/log/nodered-install.log >>/dev/null
            sudo adduser $NODERED_USER gpio 2>&1 | sudo tee -a /var/log/nodered-install.log >>/dev/null
        fi

        echo -ne "\r\n\r\n\r\n"
        echo -ne "All done.\r\n"
        if [[ $GLOBAL == "true" ]]; then
            echo -ne "  You can now start Node-RED with the command  \033[0;36mnode-red-start\033[0m\r\n"
            echo -ne "  or using the icon under   Menu / Programming / Node-RED\r\n"
        else
            echo -ne "  You can now start Node-RED with the command  \033[0;36m./node-red\033[0m\r\n"
        fi
        echo -ne "  Then point your browser to \033[0;36mlocalhost:1880\033[0m or \033[0;36mhttp://{your_pi_ip-address}:1880\033[0m\r\n"
        echo -ne "\r\nStarted  $time1  -  Finished  $(date)\r\n\r\n"
        echo "Memory : $(free -h -t | grep Total | awk '{print $2}' | cut -d i -f 1)" | sudo tee -a /var/log/nodered-install.log >>/dev/null
        echo "Finished : "$time1 | sudo tee -a /var/log/nodered-install.log >>/dev/null
        node-red-start &
    ;;
    * )
        echo " "
        exit 1
    ;;
esac
else
echo " "
echo "Sorry - cannot connect to internet - not going to touch anything."
echo "https://www.npmjs.com/package/node-red   is not reachable."
echo "Please ensure you have a working internet connection."
echo "Return code from curl is "$?
echo " "
exit 1
fi
else
echo " "
echo "Sorry - I'm not supposed to be run on a Mac."
echo "Please see the documentation at http://nodered.org/docs/getting-started/upgrading."
echo " "
exit 1
fi
fi

As a next step we need to create a template file that is the basis for the node-red-install-config.conf file that qbee creates through the UI with key-value templating. We call it node-red-install-config.tmpl and it looks like this:

# template file for Node-Red install

# run again trigger for qbee.io {{epoch}}

export EXTRANODES="{{Extranodes}}"
There are two key-value mustache place holder. The first is {{Extranodes}}. This allows to define all nodes that should be installed. They all need to be on one line with a space in between such as here: node-red-contrib-modbus@latest node-red-contrib-influxdb@latest. You can add as many nodes as necessary. In the comment we have {{epoch}}. This allows to change the epoch number in the UI and thus force to expand a new file from the.tmpl file into the .conf file. Any change will cause the script to run again thus re-installing, updating or installing new nodes. Just paste the current epoch time, save, commit and all devices in scope will install, update and add the nodes specified.

The support nodes for Raspberry Pi are as follows:

This list contains all standard Raspberry Pi nodes that the original script installs if this is selected: node-red-node-pi-gpio@latest node-red-node-random@latest node-red-node-ping@latest node-red-contrib-play-audio@latest node-red-node-smooth@latest node-red-node-serialport@latest

As a last step the command to run needs to be specified. This triggeres any time there is a change: chmod 755 /home/pi/node-red-install.sh && sudo -u pi /home/pi/node-red-install.sh > /dev/null 2>&1 & The qbee agent writes this as root. Therefore the file needs to be made executable by the user "pi". Then the sudo -u pi causes Node-Red to be installed in the user pi context. The last part just guides the output log to /dev/null and makes it exit in the background. We are currently working on giving you the log feedabck in the qbee platform as well.

Both files (script and config) need to be in the same file distribution in order to have the "command to run" trigger on template changes. In addition the template file needs to have the template checkbox activated. Then you can add ke-value boxes. This will look like this in the qbee UI dashboard:

node-red-headless-install

When this is successful you can see the following entries in the log:

node-red-headless-install-log

The flows and settings are not affected by this

Even though this installs or re-installs Node-Red your flows.jos, flows_cred.json and settings.js files will all survive this operation. Only Node-Red and the modules will get updated to the latest version, then Node-Red restarts and all is continuing to run just as before. This is why this works really well in production.

Headless Node-Red install options

In the configuration management engine you can select which group of device or which device should receive this configuration. Let's assume there is a group of Node-Red devices and that is selected. Now you can re-install or update Node-Red by inserting, saving and committing a new epoch time/number. If you drop a new device into that group into the device it will automatically receive this configuration and install Node-Red including all necessary node modules. If you also want to control the flows, settings and credentials headless you can also do that with qbee. Here you have another tutorial how to manage Node-Red flows.