Current section

Files

Jump to
nerves_livebook priv samples networking firmware_update.livemd
Raw

priv/samples/networking/firmware_update.livemd

# Firmware Updates
## Introduction
Nerves comes with a tool for handling firmware updates called
[fwup](https://github.com/fwup-home/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](https://github.com/nerves-livebook/nerves_livebook/releases).
## Walk through
Evaluate the following to see what firmware version you're running:
```elixir
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](https://github.com/nerves-livebook/nerves_livebook/releases). Let's query GitHub
to see what the latest release is:
```elixir
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.
```elixir
{: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:
```elixir
# 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.
```elixir
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 <key>`
in the arguments.
### SSH firmware updates
If you've cloned the
[`nerves_livebook`](https://github.com/nerves-livebook/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:
```sh
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/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
```