2 unstable releases

0.2.0 Sep 28, 2023
0.1.0 Sep 6, 2023

#426 in Unix APIs

MIT/Apache

145KB
3K SLoC

caterpillar

A tool for the detection and installation of RAUC update bundles found on attached block devices. RAUC is a way to update firmware on embedded devices.

Caterpillar makes use of D-Bus to communicate with

  • UDisks2 (for enumeration and (un)mounting of block devices)
  • RAUC (for validation and installation of update bundles)
  • logind (for reboot after successful installation)

The application also exposes its own D-Bus interface. More information on how to use it can be found in the interactive update section.

Configuration

Some aspects of caterpillar's behavior can be configured using a configuration file in /etc/caterpillar/caterpillar.toml. It is also possible to override behavior using environment variables in all caps, prefixed with CATERPILLAR_ (e.g. autorun = true -> CATERPILLAR_AUTORUN=true).

Use-cases

Caterpillar supports two modes of operation, non-interactive and interactive, which are explained in more detail in the sections below.

The application is run in the background using the caterpillar.service systemd unit. Other applications running as root can communicate with it over D-Bus.

Caterpillar takes care of detecting all attached block devices and mounts compatible filesystems found on them. In the top-level directory of each mounted filesystem it searches for compatible RAUC update bundles with a version higher than the current system version and allows for installing them. When placing a single compatible bundle in a configurable override directory, caterpillar is able to install bundles of lower version as well.

NOTE: Only semver version comparison is supported!

After successful update, caterpillar unmounts all previously mounted devices and can optionally trigger a reboot of the system (to boot into the updated system).

A rough overview of caterpillar's interaction with rauc and udisks2 is outlined in the below diagram:

An overview graph of the caterpillar process in a boot scenario

Interactive update

Caterpillar exposes a D-Bus interface, which allows external applications running as root to communicate with it.

Introspection

The application starts in idle mode, waiting on external input.

[root@system ~]# busctl introspect de.sleepmap.Caterpillar /de/sleepmap/Caterpillar de.sleepmap.Caterpillar
NAME                    TYPE      SIGNATURE RESULT/VALUE FLAGS
.InstallUpdate          method    bb        -            -
.SearchForUpdate        method    -         -            -
.MarkedForReboot        property  b         false        emits-change
.State                  property  s         "idle"       emits-change
.Updated                property  b         false        emits-change
.UpdateFound            signal    a(sssb)   -            -

Searching for updates

NOTE: It is advised to subscribe to the UpdateFound signal, which will propagate a found update.

Using the SearchForUpdate method, caterpillar can be requested to search for compatible updates:

[root@system ~]# busctl call de.sleepmap.Caterpillar /de/sleepmap/Caterpillar de.sleepmap.Caterpillar SearchForUpdate

If a compatible update is found, caterpillar's State property changes to updatefound (noupdatefound, if no update is found, shortly after which it unmounts mounted devices again and returns to idle).

[root@system ~]# busctl introspect de.sleepmap.Caterpillar /de/sleepmap/Caterpillar de.sleepmap.Caterpillar
NAME                    TYPE      SIGNATURE RESULT/VALUE  FLAGS
.InstallUpdate          method    bb        -             -
.SearchForUpdate        method    -         -             -
.MarkedForReboot        property  b         false         emits-change
.State                  property  s         "updatefound" emits-change
.Updated                property  b         false         emits-change
.UpdateFound            signal    a(sssb)   -             -

The UpdateFound signal is emitted, providing an array of length one with information on the available update:

  • absolute path of update file (s)
  • current version (s)
  • new version (s)
  • whether the update is an override (b)
[root@system ~]# dbus-monitor --system "type='signal',path='/de/sleepmap/Caterpillar',interface='de.sleepmap.Caterpillar',member='UpdateFound'"
signal time=1695853835.109057 sender=:1.37 -> destination=(null destination) serial=8 path=/de/sleepmap/Caterpillar; interface=de.sleepmap.Caterpillar; member=UpdateFound
   array [
      struct {
         string "/run/media/root/bundle_disk_btrfs/update.raucb"
         string "0.0.0"
         string "1.0.0"
         boolean false
      }
   ]

Installing updates

Using the InstallUpdate method, caterpillar can be triggered to either install (and optionally reboot) or skip a found update.

When requesting to skip the update and not reboot (requesting to reboot has no effect when not also updating), caterpillar unmounts all previously mounted devices and returns to its idle state (with the Updated property unchanged).

[root@system ~]# busctl call de.sleepmap.Caterpillar /de/sleepmap/Caterpillar de.sleepmap.Caterpillar InstallUpdate bb false false
[root@system ~]# busctl introspect de.sleepmap.Caterpillar /de/sleepmap/Caterpillar de.sleepmap.Caterpillar
NAME                    TYPE      SIGNATURE RESULT/VALUE FLAGS
.InstallUpdate          method    bb        -            -
.SearchForUpdate        method    -         -            -
.MarkedForReboot        property  b         false        emits-change
.State                  property  s         "idle"       emits-change
.Updated                property  b         false        emits-change
.UpdateFound            signal    a(sssb)   -            -

When requested to update but not reboot, caterpillar updates the system, unmounts all previously mounted devices and returns to its idle state, setting its Updated property to true on successful update.

[root@system ~]# busctl call de.sleepmap.Caterpillar /de/sleepmap/Caterpillar de.sleepmap.Caterpillar InstallUpdate bb true false
[root@system ~]# busctl introspect de.sleepmap.Caterpillar /de/sleepmap/Caterpillar de.sleepmap.Caterpillar
NAME                    TYPE      SIGNATURE RESULT/VALUE FLAGS
.InstallUpdate          method    bb        -            -
.SearchForUpdate        method    -         -            -
.MarkedForReboot        property  b         false        emits-change
.State                  property  s         "updating"   emits-change
.Updated                property  b         false        emits-change
.UpdateFound            signal    a(sssb)   -            -
[root@system ~]# busctl introspect de.sleepmap.Caterpillar /de/sleepmap/Caterpillar de.sleepmap.Caterpillar
NAME                    TYPE      SIGNATURE RESULT/VALUE FLAGS
.InstallUpdate          method    bb        -            -
.SearchForUpdate        method    -         -            -
.MarkedForReboot        property  b         false        emits-change
.State                  property  s         "idle"       emits-change
.Updated                property  b         true         emits-change
.UpdateFound            signal    a(sssb)   -            -

When requested to update and reboot, caterpillar updates the system, unmounts all previously mounted devices and goes to done state. Its Updated and MarkedForReboot properties are both set to true.

[root@system ~]# busctl call de.sleepmap.Caterpillar /de/sleepmap/Caterpillar de.sleepmap.Caterpillar InstallUpdate bb true false
[root@system ~]# busctl introspect de.sleepmap.Caterpillar /de/sleepmap/Caterpillar de.sleepmap.Caterpillar
NAME                    TYPE      SIGNATURE RESULT/VALUE FLAGS
.InstallUpdate          method    bb        -            -
.SearchForUpdate        method    -         -            -
.MarkedForReboot        property  b         true         emits-change
.State                  property  s         "updating"   emits-change
.Updated                property  b         false        emits-change
.UpdateFound            signal    a(sssb)   -            -
[root@system ~]# busctl introspect de.sleepmap.Caterpillar /de/sleepmap/Caterpillar de.sleepmap.Caterpillar
NAME                    TYPE      SIGNATURE RESULT/VALUE FLAGS
.InstallUpdate          method    bb        -            -
.SearchForUpdate        method    -         -            -
.MarkedForReboot        property  b         true         emits-change
.State                  property  s         "done"       emits-change
.Updated                property  b         true         emits-change
.UpdateFound            signal    a(sssb)   -            -

Non-interactive update during boot

Caterpillar can be configured to run non-interactively the first time it is run, using the autorun configuration option. In this mode the application will automatically (without user input):

  • detect all block devices and mount compatible filesystems
  • search and select one compatible update bundle
    • if a (top-level) override directory with a single update bundle in it is found in the mountpoint
    • if more than one update bundle exists in the (top-level) directory of the mountpoint, the one with the highest version is selected
  • install the selected update bundle
  • reboot

Building

Caterpillar is written in Rust and built using cargo:

cargo build --frozen --release --all-features

Tests

Unit tests can be executed using

cargo test -- --skip integration

NOTE: The integration test setup requires quite some space (ca. 10 - 20 GiB) and can only be run serially (which takes quite long).

cargo test integration

The integration tests require the following tools to be available on the test system:

License

All code contributions are dual-licensed under the terms of the Apache-2.0 and MIT. For further information on licensing refer to the contributing guidelines.

Funding

This project has been made possible by the funding of Nonlinear Labs GmbH.

Dependencies

~18–30MB
~485K SLoC