Firmware Updates
Introduction
Nerves comes with a tool for handling firmware updates called fwup. It allows for archival and application of firmware updates, however it does not handle distribution of updates. This Livebook comes with a module that can fetch a firmware update from Github releases.
Walk through
Evaluate the following to see what firmware version you’re running:
IO.write("""
You're running Nerves Livebook #{NervesLivebook.version()}.
More details:
Target: #{Nerves.Runtime.mix_target()}
Firmware UUID: #{Nerves.Runtime.KV.get_active("nerves_fw_uuid")}
Firmware partition: #{Nerves.Runtime.KV.get("nerves_fw_active")}
""")
Nerves Livebook firmware images are hosted on GitHub under the releases tab. Let’s query GitHub to see what the latest release is:
NervesLivebook.check_internet!()
# Setup for below
alias NervesLivebook.GithubRelease
repo = "livebook-dev/nerves_livebook"
firmware_path = "/data/update.fw"
firmware_public_key = "IyCnjyE1rrV+W5HFrovC+ZyxrBh9fF7Na4S+7dcGAPw="
# Get the release metadata
{:ok, latest} = GithubRelease.get_latest(repo)
IO.puts("The latest GitHub release for #{repo} is version #{GithubRelease.version(latest)}.")
Still interested in updating? It’s ok to update to the same release if you just want to try this out.
The next step is to download the firmware. This might take some time if you’re on a slow connection. Watch the evaluating circle pulse to know that it’s working.
{:ok, firmware_url} =
GithubRelease.firmware_url(latest, "nerves_livebook_#{NervesLivebook.target()}.fw")
IO.puts("Downloading #{firmware_url} to #{firmware_path}...")
# httpc doesn't overwrite, so erase an old file
File.rm_rf!(firmware_path)
{:ok, :saved_to_file} =
:httpc.request(:get, {firmware_url, []}, [], stream: to_charlist(firmware_path))
The firmware is now stored locally in "/data/update.fw"
. We’re ready to
install it, but first, let’s check out its metadata and make sure it’s valid:
# Check signatures if firmware_public_key is specified
extra_fwup_arguments = if firmware_public_key, do: ["--public-key", firmware_public_key], else: []
{output, 0} = System.cmd("fwup", ["--metadata", "-i", firmware_path] ++ extra_fwup_arguments)
IO.puts("Firmware metadata:\n" <> output)
# Validation isn't needed since the upgrade step also validates, but it's
# informative.
IO.puts("Validating archive...")
{output, _} = System.cmd("fwup", ["--verify", "-i", firmware_path] ++ extra_fwup_arguments)
IO.write(output)
The UUID metadata field is important. The UUID is computed from the contents of the firmware archive and uniquely identifies what’s running. If you’re ever unsure if you’re running the exact same bits that you verified in QA, check the UUID.
Ok, now we’re ready for the big step. This won’t erase any of your notebooks or settings. It just updates the Nerves Livebook firmware. After this completes successfully, your device will reboot.
NervesLivebook.Fwup.upgrade(firmware_path, extra_fwup_arguments)
Notes
Firmware signatures
Nerves Livebook is cryptographically signed as part of the CI script. If you
request that fwup
checks signatures (as we will do soon), you can have high
confidence that the firmware came from someone who possesses the corresponding
private key.
To replicate this, do the following:
-
Use
fwup
to generate a public/private key pair by runningfwup -g
. -
Copy the contents of the public key file to someplace convenient in your
application.
fwup
supports more than one public key to allow for multiple publishers and key rotation. If this interests you, make it a list. -
In your build pipeline, add a call to
fwup
to sign the firmware. In other words, when your build completes, you should have a.fw
file. Callfwup -S --private-key "key" -i input.fw -o output.fw
-
Then, make sure every call to
fwup
in your application has--public-key
in the arguments.
SSH firmware updates
If you’ve cloned the
nerves_livebook
repository
and are building your own version of it, it’s going to be easier to update your
device via SSH. The firmware signatures aren’t checked on SSH updates, so you
can do what you want. 🚀
Here’s the script:
cd nerves_livebook
export MIX_ENV=rpi0
mix deps.get
mix firmware
mix firmware.gen.script
./upload.sh livebook@nerves.local
Path: ./_build/rpi0_dev/nerves/images/nerves_livebook.fw
Product: nerves_livebook 0.2.4
UUID: 0989e4e1-55e4-51ca-ba39-5c677cc7395c
Platform: rpi0
Uploading to livebook@nerves.local...
Warning: Permanently added 'nerves.local,172.31.112.97' (RSA) to the list of known hosts.
Nerves Livebook
https://github.com/livebook-dev/nerves_livebook
ssh livebook@nerves.local # Use password "nerves"
Password:
fwup: Upgrading partition B
48% [================= ] 19.17 MB in / 21.17 MB out