Tutorial

How to build a full declarative multi device setup from scratch

This is an advanced tutorial for people who want to maintain larger installations.
It is recommended to have basic Linux administration skills.

The NEXUS platform supports handling multiple devices for covering large buildings.

The following tools are used for this task:

All these tools are out of the box supported by our platform.

The quick guide

This chapter shows how easily it works without going into details.

Boot the freeloader image

On your NEXUS device run:

sudo freeloader_enable_boot --token THISISASECRET
sudo reboot

Upload the new image and configuration

freeloadercmd --reboot --image nexus.img --upload-file setup.yaml --token THISISASECRET 192.168.1.*

The device will now reboot into the new image and the setup.yaml instructions will be executed by Ansible.

Preparation

Ansible (in management mode) does only support Linux based operating systems!
If you want to use Windows for this guide we recommend running Ubuntu in VirtualBox (Tutorial)

Example requirements

  • 2 or more NEXUS devices with Ethernet modules

Flashing the freeloader image

  1. Download the freeloader boot image from https://github.com/nexus-unity/freeloader
  2. Flash the image on each of the devices (Instructions)
  3. Start all devices

Installing Ansible

  1. Follow this guide how to install Ansible
  2. Download the freeloader ansible plugins TODO: Update

Flashing and configuring multiple devices

At this point you should have at least two NEXUS devices with Ethernet modules connected to the same network running.
You should have a (virtual) Ubuntu/Linux setup with Ansible installed.

Testing the configuration

This step will test if the setup is done correctly by scanning the network for devices.

  1. Open the directory where you have placed the freeloader ansible plugins. There should be the library/ and module_utils/ directory.
  2. Create a file called scan.yaml and open it using your favorite editor, we recommend Atom for this task.
  3. Copy the following text into the editor and save it. You need to change the ip address to match your network. (one wildcard is supported)
- hosts: localhost
  tasks:
    - name: Scan for devices
      freeloader_scan:
        ip: "192.168.1.*"
      register: result
    - debug: var=result
  1. Run ansible-playbook scan.yml
  2. If you have configured the setup correctly the “devices” should contain the booted devices.
PLAY [localhost] *******************************************************************************

TASK [Gathering Facts] *************************************************************************
ok: [localhost]

TASK [Scan for devices] ************************************************************************
ok: [localhost]

TASK [debug] ***********************************************************************************
ok: [localhost] => {
    "result": {
        "changed": false,
        "devices": [
            {
                "disks": [
                    {
                        "disk_name": "mmcblk0",
                        "partition_json": {
                            "partitiontable": {
                                "device": "/dev/mmcblk0",
                                "id": "0x40f31316",
                                "label": "dos",
                                "partitions": [
                                    {
                                        "node": "/dev/mmcblk0p1",
                                        "size": 204800,
                                        "start": 2048,
                                        "type": "b"
                                    },
                                    {
                                        "node": "/dev/mmcblk0p2",
                                        "size": 31215028,
                                        "start": 206848,
                                        "type": "fd"
                                    }
                                ],
                                "sectorsize": 512,
                                "unit": "sectors"
                            }
                        },
                        "size_bytes": 16088301568
                    }
                ],
                "freeloader_version": "0.1.0",
                "ip": "192.168.1.135",
                "mac_address": "6a:fa:03:22:b0:cb",
                "model": {
                    "hardware": "BCM2835",
                    "model": "Raspberry Pi Compute Module 3 Rev 1.0",
                    "revision": "a220a0",
                    "serial": "000000000d76573e"
                },
                "system_info": {
                    "hostname": "Freeloader",
                    "kernel_version": "5.4.105",
                    "os": "Linux 21.02-snapshot OpenWrt Freeloader",
                    "uptime": "41 seconds"
                },
                "url": "https://192.168.1.135:8443/freeloader/0.1"
            }
        ],
        "failed": false
    }
}

PLAY RECAP ************************************************************************************
localhost                  : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

Preparing the configuration

This step will prepare the initial configuration uploaded to the device.

  1. Create a directory called files/
  2. Download the latest operation system image from TODO
  3. Place the image in the files/ directory and unpack it (.img file extension)
  4. Create a file called files/setup.yaml
  5. The setup.yaml will add the user account “remoteuser” to the system, install a public ssh key and enable root access
    You must replace ssh-rsa AAAA…gX development@pc by your public ssh key
    The configuration will be executed on first boot and setup the user.
- hosts: localhost
  tasks:
    - name: Add user
      user:
        name: remoteuser
        comment: Remote User
        groups: sudo
    - name: Create ssh directory
      file:
        path: "/home/remoteuser/.ssh/"
        state: directory
        owner: remoteuser
        group: remoteuser
        mode: '0700'
    - name: Copy pub key to authorized_keys file
      copy:
        owner: remoteuser
        group: remoteuser
        mode: '0600'
        dest: "/home/remoteuser/.ssh/authorized_keys"
        content: |
                                  ssh-rsa AAAA...gX development@pc
    - name: Add to sudoers
      copy:
        dest: "/etc/sudoers.d/99_remoteuser"
        content: remoteuser ALL=(ALL:ALL) NOPASSWD:ALL
        validate: 'visudo -cf %s'
        mode: 0440
  1. Create a template/ directory and a file template/hosts-file.j2
nexushosts:
  hosts:
{% for group in groups %}
{% if groups[group] and group == 'nexushosts' %}
{% for host in groups[group] %}
    {{hostvars[host].inventory_hostname}}:
      ansible_host: {{hostvars[host].ansible_host}}
      mac_address: "{{ hostvars[host].mac_address }}"
      serial_cm: "{{ hostvars[host].serial_cm }}"
{% endfor %}

{% endif %}
{% endfor %}
  vars:
    ansible_user: remoteuser
    ansible_python_interpreter: auto

Executing the initial configuration

This step will upload the os image to the device and write it on the storage.
It will also place the setup.yaml file in the boot directory and reboot the system to start the os. All successfully configured devices will be stored in the nexus-hosts.yaml file

  1. Create the file initial_setup.yaml and add the content (do not forget to change the ip address)
- hosts: localhost
  tasks:
    - name: Scan for devices
      freeloader_scan:
        ip: "192.168.1.*"
      register: detected_devices
    - debug: var=detected_devices

    - name: Upload image
      with_items:
        - "{{detected_devices['devices']}}"
      freeloader_image:
        ip: "{{ item['ip'] }}"
        file: "./files/nexus.img"
      register: finished_devices
    - debug: var=finished_devices

    - name: Upload setup.yaml and reboot
      with_items:
        - "{{finished_devices.results}}"
      freeloader_modify:
        ip: "{{ item.item['ip'] }}"
        redeploy_freeloader: True
        upload_file_to_boot: "files/setup.yaml"
        reboot: True
      register: rebooted_devices
    - debug: var=rebooted_devices

    - name: Register device
      with_items:
        - "{{finished_devices.results}}"
      add_host:
        name: "{{ item.item['ip'] }}"
        groups: [ nexushosts ]
        ansible_host: "{{ item.item['ip'] }}"
        mac_address: "{{ item.item['mac_address'] }}"
        serial_cm: "{{ item.item['model']['serial'] }}"

    - name: Write devices to hostfile
      template:
        src: templates/hosts-file.j2
        dest: "./nexus-hosts.yaml"
  1. Run ansible-playbook initial_setup.yaml
    This will execute the steps and upload the image and setup.yaml to the devices.
    It may take several minutes (up to a hour) for all tasks to complete.
    There is not much verbosity during the procedure so just wait for the process to finish.

Configuration after setup

Modifying the hosts file

After the initial setup all the devices should run the operating system and include the changes from setup.yaml .
It is assumed that the devices get the same ip addresses via dhcp after reboot (which is the typical behavior).
Otherwise it is necessary to adapt the ip addresses.

  1. You may want to edit the nexus-hosts.yaml to assign meaningful hostnames to the devices.
nexushosts:
  hosts:
    192.168.1.135:
      ansible_host: 192.168.1.135
      mac_address: "6a:fa:03:22:b0:cb"
      serial_cm: "000000000d76573e"
    192.168.1.136:
      ansible_host: 192.168.1.136
      mac_address: "6a:fa:0f:21:b1:cf"
      serial_cm: "000000000d80574e"

  vars:
    ansible_user: remoteuser
    ansible_python_interpreter: auto

Changed names:

nexushosts:
  hosts:
    nexus-first-floor:
      ansible_host: 192.168.1.135
      mac_address: "6a:fa:03:22:b0:cb"
      serial_cm: "000000000d76573e"
    nexus-second-floor:
      ansible_host: 192.168.1.136
      mac_address: "6a:fa:0f:21:b1:cf"
      serial_cm: "000000000d80574e"

  vars:
    ansible_user: remoteuser
    ansible_python_interpreter: auto

Hostname configuration

At this point all configured devices should be up and running.
The nexus-hosts.yaml file should have meaningful hostnames. Now you can create and execute Ansible playbooks to configure the devices. It is recommended to read the Ansible documentation to understand the concept. For example we are changing the hostnames of all devices to match the names in our nexus-hosts.yaml

  1. Create a file called change_hostname.yaml and add the content
- hosts: nexushosts
  become: true
  become_user: root
  tasks:
      - name: Set hostname
        ansible.builtin.hostname:
          name: "{{ inventory_hostname }}"

      - name: Updated /etc/hosts
        ansible.builtin.lineinfile:
          path: /etc/hosts
          regexp: "^127\\.0\\.1\\.1.*{{ inventory_hostname }}"
          line: "127.0.1.1       {{ inventory_hostname }}"
  1. Run ansible-playbook change_hostname.yaml -i nexus-hosts.yaml
  2. Now each device should have the hostname configured in the nexus-hosts.yaml file.

Updating

This examples shows how to update all devices

  1. Create a file called update.yaml and add the content
- hosts: nexushosts
  become: true
  become_user: root
  tasks:
    - name: Update packages
      apt:
        upgrade: 'dist'
        update_cache: 'yes'
        autoremove: true
        autoclean: true
  1. Run ansible-playbook update.yaml -i nexus-hosts.yaml
  2. All devices should now have the latest updates installed

TODO

Install and configure packages.

Last modified September 13, 2021