When you’re starting out with Nerves, you may have connected to your first
projects over the network using nerves.local
. Libraries like
nerves_init_gadget make this easy and when you’re
starting out, it’s really convenient. Don’t know the IP address that your
device was assigned? Try nerves.local
and you’re good to go.
And then you add a second device to your network. nerves.local
isn’t looking
so convenient any more.
Update: we’re moving to namespacing provisioning variables. The references to serial_number
will eventually change to nerves_serial_number
. We’ll keep serial_number
working in official builds, though.
Suffice it to say that there are a number of ways of solving this problem. This post shows how to provision names or numbers to devices. You’ll likely need to provision additional information to devices and that can be done in a similar way.
The first step is to decide on a way of identifying devices. Nearly every
Nerves System has a way of finding a unique identifier for a board by default.
The default is to use that number to create a unique hostname and you can see
it by running :inet.gethostname()
. It will be something like nerves-1234
.
(This hostname is only local unless something registers it in the DNS.
nerves.local
is a mDNS name.) The erlinit.config
file specifies how to
create the hostname. All of the Raspberry Pi boards have a similar setting.
Here is the one for the Raspberry Pi 3:
-d "/usr/bin/boardid -b uboot_env -u serial_number -b rpi -n 4"
-n nerves-%s
The -d
setting specifies how to find a unique ID. This invokes
boardid to look it up. Don’t worry about the commandline arguments
yet. The unique ID could be stored in the CPU (like on the Raspberry Pi), an
EEPROM, or a few other places depending on the board. The -n
setting specifies
the format of the hostname where the %s
is substituted for the identifier.
Getting back to the boardid
commandline, the arguments say to use the
serial_number
key from the U-Boot environment first or if that doesn’t exist,
use 4 digits of the Raspberry Pi’s serial number.
Nerves uses the U-Boot environment for many things, but mostly for keeping track of the running firmware by default. You can think of it as a very simple and small key-value store that’s on the SDCard (if you’re using a Raspberry Pi) but stored outside of any of the filesystems. Nerves.Runtime documents the keys that Nerves uses. A device doesn’t have to run the U-Boot bootloader to have a U-Boot environment. The format is convenient so Nerves reuses it.
Nerves does not make writing values to the U-Boot environment convenient to reduce the chance of corrupting or losing data in it. Users should prefer to store application settings and data in the Nerves application partition. The U-Boot environment is appropriate for provisioning information that is unlikely to change over the device’s lifetime. The serial number of the device is one example. To write it, attach to your Nerves device’s console at type:
iex> cmd("fw_setenv serial_number abc123")
:ok
Reboot and the next time the device comes up, you should be able to see the new serial number:
iex> Nerves.Runtime.KV.get("serial_number")
"abc123"
Iex> :inet.gethostname()
{:ok, 'nerves-abc123'}
Now getting back to the original issue, how does this fix the nerves.local
problem? The answer is that you need to update the nerves_init_gadget
configuration to tell it to construct the mDNS name off of the hostname. Modify
your config.exs
to look something like this:
config :nerves_init_gadget,
mdns_domain: :hostname,
ssh_console_port: 22
Rebuild and update the device. On your laptop, you should be able to ping nerves-abc123.local
now.
Provisioning devices offline
Imagine that you need to make SDCards for a lot of devices. Logging on to the
console to set the serial number on each device will get old quick. You can
program the serial number at the same time that you’re burning the SDCard.
Instead of using mix firmware.burn
, it is easier to call fwup
directly. mix firmware.burn
is a minimal wrapper on fwup
anyway.
sudo SERIAL_NUMBER=abc123 fwup ./_build/rpi3/dev/nerves/images/myproj.fw
If you’re wondering how this works, look for the following line in your system’s
fwup.conf
file:
uboot_setenv(uboot-env, "serial_number", "\${SERIAL_NUMBER}")
Security
The U-Boot environment block is stored in the clear on the SDCard and is accessible via Nerves.Runtime.KV. The data isn’t authenticated and tools are readily available to modify it. Since you’ll be tempted to provision secret key material in the U-Boot environment block, please review the security ramifications of that decision before doing so.