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 🙂
