Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

Firmware Updates

firmware_update.livemd

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 = "nerves-livebook/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_#{Nerves.Runtime.mix_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:

  1. Use fwup to generate a public/private key pair by running fwup -g.
  2. 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.
  3. 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. Call fwup -S --private-key "key" -i input.fw -o output.fw
  4. 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_TARGET=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/nerves-livebook/nerves_livebook

ssh livebook@nerves.local # Use password "nerves"

Password:
fwup: Upgrading partition B
 48% [=================                   ] 19.17 MB in / 21.17 MB out