Deploy nodered with ansible + systemd

I wanted to be able to deploy nodered using ansible to Ubuntu. I wanted to deploy multiple copies, run them as a service with systemd, run on different ports, and configure an admin password.

It is not idempotent.

This is what I ended up with. None of this would have been possible without the most excellent https://github.com/TotallyInformation/alternate-node-red-installer

First a playbook to install nodejs and the alternate-node-red-installer (globally) and a couple other sundries.

---
- name: Playbook to Install Nodejs
  hosts:
    - dev
  tasks:
  - name: Run the equivalent of "apt-get update" as a separate step
    apt:
     update_cache: yes
  - name: Ansible shell module multiple commands
    shell: 'curl -fsSL https://deb.nodesource.com/setup_16.x | sudo -E bash -'
  - name: Install reqired packages
    apt: name={{ item }} state=present
    with_items:
      - git
      - curl
      - wget
      - nodejs
  - name: Install "alternate-node-red-installer" node.js package globally.
    community.general.npm:
      name: alternate-node-red-installer
      global: yes

Next, a playbook that given an instance name, port, and password installs nodered.

You can call it like this

ansible-playbook install_nodered.yaml --extra-vars "nodered_instance=nodered1 nodered_port=1900 admin_password=mypassword"

Notes:

admin_password is optional, if not provided that block doesn’t execute

nodered_instance and nodered_port are required

alternate-node-red-installer creates a systemd template, this playbook creates a copy, modifies it and links and enables it, daemon reloads and then starts it.

the port is defined in the envfile.ini

projects are enabled with some goofy regex, same for enabling the password settings. Doesn’t seem like there is a great way to edit .js files from Ansible.

---
- name: Playbook to Install nodered
  hosts:
    - dev
  vars:
    nodered_instance: ""
    nodered_port: ""

    auth_text: |
      adminAuth: {
        type: "credentials",
        users: [{
            username: "admin",
            password: "replace_bcrypt_password_here",
            permissions: "*"
        }]
      },
      /**
  tasks:
  - name: fail when the 'nodered_instance' variable is undefined
    fail:
      msg: the 'nodered_instance' variable is undefined
    when: nodered_instance is undefined
  - name: fail when the 'nodered_instance' variable is empty
    fail:
      msg: the 'nodered_instance' variable is empty
    when: nodered_instance | length == 0

  - name: fail when the 'nodered_port' variable is undefined
    fail:
      msg: the 'nodered_port' variable is undefined
    when: nodered_port is undefined

  - name: fail when the 'nodered_port' variable is empty
    fail:
      msg: the 'nodered_port' variable is empty
    when: nodered_port | length == 0

  - name: Check if something is running on the port
    wait_for:
      port: '{{ nodered_port }}'
      timeout: 10
      state: stopped
      msg: "Port {{ nodered_port }} is being used, choose a differnt port"
    register: service_status

  - name: Install node red
    shell: 'alternate-node-red-installer -f /home/nodered/{{  nodered_instance  }}'
  - name: Copy systemd template
    ansible.builtin.copy:
      src: "/home/nodered/{{  nodered_instance  }}/system/node-red.service"
      dest: "/home/nodered/{{  nodered_instance  }}/system/node-red-{{  nodered_instance  }}.service"
      remote_src: yes
      owner: nodered
      group: nodered
      mode: '0644'
  - name: set user to nodered
    ini_file: "dest=/home/nodered/{{  nodered_instance  }}/system/node-red-{{  nodered_instance  }}.service section=Service option=User value=nodered"
  - name: set group to nodered
    ini_file: "dest=/home/nodered/{{  nodered_instance  }}/system/node-red-{{  nodered_instance  }}.service section=Service option=Group value=nodered"

  - name: fix working dir
    ini_file: "dest=/home/nodered/{{  nodered_instance  }}/system/node-red-{{  nodered_instance  }}.service section=Service option=WorkingDirectory value=/home/nodered/{{  nodered_instance  }}"

  - name: fix working EnvironmentFile
    ini_file: "dest=/home/nodered/{{  nodered_instance  }}/system/node-red-{{  nodered_instance  }}.service section=Service option=EnvironmentFile value=/home/nodered/{{  nodered_instance  }}/data/envfile.ini"

  - name: Change the port in the envfile
    copy:
      dest: "/home/nodered/{{  nodered_instance  }}/data/envfile.ini"
      content: "PORT={{ nodered_port }}"

  - name: Recursively change ownership of a directory
    ansible.builtin.file:
      path: /home/nodered/{{  nodered_instance  }}
      state: directory
      recurse: yes
      owner: nodered
      group: nodered

  - name: Replace projects enabled with true
    replace:
      path: "/home/nodered/{{  nodered_instance  }}/data/settings.js"
      after: 'projects: {'
      before: 'workflow: {'
      regexp: '\benabled: false\b'
      replace: 'enabled: true'

  - block:

    - name: add in auth_text to enable security
      replace:
        path: "/home/nodered/{{  nodered_instance  }}/data/settings.js"
        regexp: '^.*(To password protect the Node-RED editor and admin API, the following).*$([\s\S]*)^.*(The following property can be used to enable HTTPS).*$'
        replace: '{{ auth_text }}'

    - name: generate password
      shell: 'node -e "console.log(require(''bcryptjs'').hashSync(process.argv[1], 8));" {{admin_password}}'
      register: bcrypt_password
      args:
        chdir: '/home/nodered/{{nodered_instance}}'

    - name: Replace password in settings file
      replace:
        path: "/home/nodered/{{  nodered_instance  }}/data/settings.js"
        after: 'adminAuth: {'
        before: 'permissions:'
        regexp: '\breplace_bcrypt_password_here\b'
        replace: '{{ bcrypt_password.stdout }}'

    when: admin_password is defined

  - name: Link to multi-user.target.wants
    shell: 'sudo /bin/systemctl enable /home/nodered/{{  nodered_instance  }}/system/node-red-{{  nodered_instance  }}.service'

  - name: Restart nodered instance
    ansible.builtin.systemd:
      state: restarted
      daemon_reload: yes
      name: "node-red-{{ nodered_instance  }}"

Additional Notes:

You could check for missing variables using asserts

https://github.com/robertdebock/ansible-role-node_red/blob/master/tasks/assert.yml

But I like the failure messages better from the fail 🙂

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s