> ## Documentation Index
> Fetch the complete documentation index at: https://docs.thistle.tech/llms.txt
> Use this file to discover all available pages before exploring further.

# Thistle OTA Update on Raspberry Pi 4

> Thistle OTA Root Filesystem A/B Update on Raspberry Pi 4

This guide will show you how to perform an over-the-air (OTA) root filesystem
(rootfs) update with A/B partition support (fail safe) on a Raspberry Pi 4B
device using Thistle. This guide uses an official Raspberry Pi OS image released
by the Raspberry Pi Foundation.

You can follow along with our video guide as well.

<div style={{display: "flex", width: "100%", justifyContent: "center", marginBottom: "50px", marginTop: "50px"}}>
  <iframe width="560" height="315" src="https://www.youtube.com/embed/uLGH5u23xdA" title="YouTube video player" frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowFullScreen />
</div>

<img src="https://mintcdn.com/thistletechnologies/RuOvvatuLqT9Z9Z-/images/abboot.svg?fit=max&auto=format&n=RuOvvatuLqT9Z9Z-&q=85&s=7aaf2488fcb898979f35a9f9d21fd7fb" alt="Thistle Control Center" width="1177" height="1096" data-path="images/abboot.svg" />

## Tools Needed for This Tutorial

### Hardware

* One [Raspberry Pi 4 Model
  B](https://www.raspberrypi.com/products/raspberry-pi-4-model-b/) single-board
  computer with accessories including a microSD card that's 32GB or larger. Any
  [CanaKit Raspberry Pi 4 Starter
  Kit](https://www.canakit.com/raspberry-pi-4-starter-kit.html) will work. This
  is the target host device for OTA A/B update integration.
* One USB-A thumb drive (64GB or larger), for example [SanDisk 64GB Ultra USB
  3.0 Flash Drive -
  SDCZ48-064G-UAM46](https://www.amazon.com/SanDisk-Ultra-Transfer-Speeds-SDCZ48-064G-UAM46/dp/B00KYK2ABI?th=1).
  We will program the USB drive, and use it to boot the Raspberry Pi into a
  "utility" OS to prepare the microSD card for OTA A/B update integration.
* One desktop/laptop PC running Linux, MacOS X, or Windows, for publishing OTA
  updates, programming the USB drive, controlling the Raspberry Pi, and observing
  its logs.
* One [USB to TTL Serial Cable - Debug / Console Cable for Raspberry
  Pi](https://www.adafruit.com/product/954). If your PC runs Windows or MacOS X,
  you need to install the associated PL2303 and CP2102 drivers by following
  instructions in the preceding link. The cable is used to connect the PC to the
  Raspberry Pi's UART port, to observe boot logs.

### Software

* The [Thistle Release Helper](/binaries#release-helper-trh) to help packaging your updates
* [A project access token from the Thistle App](https://app.thistle.tech/projects)
* The official Raspberry Pi OS Lite image (May 13, 2025):
  [download](https://downloads.raspberrypi.com/raspios_lite_armhf/images/raspios_lite_armhf-2025-05-13/2025-05-13-raspios-bookworm-armhf-lite.img.xz)
* Thistle provided sample [update disk
  image](https://downloads.thistle.tech/rpi/four-v1.5.0-updated.rootfs)

## OTA A/B Update Demo Preparation

### Connect PC and RPi-4 over Serial Port

Connect the RPi-4's Pin 6 (GND), Pin 8 (GPIO 14 / TXD), and Pin 10 (GPIO 15 /
RXD) to the Ground (black), RXD (white), and TXD (green) of the USB To TTL
serial cable, respectively. Plug the other end of the cable (USB-A male) to the
PC.

<img src="https://mintcdn.com/thistletechnologies/MrBm0BC7xpW_ySdM/images/ota-pc-rpi4-uart-conn.jpg?fit=max&auto=format&n=MrBm0BC7xpW_ySdM&q=85&s=33f28a556950b30bb5790607184f4957" alt="Connect PC to RPi-4" width="900" height="675" data-path="images/ota-pc-rpi4-uart-conn.jpg" />

### Prepare USB Drive and microSD Card on PC

On the desktop/laptop PC, install the [Raspberry Pi
Imager](https://www.raspberrypi.com/software/) application, and use it to
install a utility operating system to the USB drive. Insert the USB drive to the
PC. In Imager's UI, select "Raspberry Pi 4" as the device, "Raspberry Pi OS
(other) > Raspberry Pi OS Lite (64-bit)" as the operating system (for this guide
we use the May 13, 2025 version), and the USB drive as the storage. In the "OS
customisation" step: set the host name (in this guide our host name will be
"rpi4-util"), username and password; configure wireless LAN credential so that
the RPi-4 can automatically connect to WiFi; and enable SSH to allow a headless
connection setup. Program the USB drive with the selected OS with the custom
setting. Remove the USB drive from PC.

Insert the microSD card to the PC. Repeat the above process to write the
Raspberry Pi OS with similar settings to the microSD card. We will use hostname
"ota-demo" in this guide. Remove the microSD card from PC. The microSD card will
be used for the OTA A/B update demonstration.

Once both the USB drive and the microSD card are programmed, insert the USB
drive to the RPi-4's USB-A port. Make sure the microSD card is not inserted into
the RPi-4.

<img src="https://mintcdn.com/thistletechnologies/MrBm0BC7xpW_ySdM/images/ota-rpi4-overall-conn.jpg?fit=max&auto=format&n=MrBm0BC7xpW_ySdM&q=85&s=70e1dd9696ae2b451c539223c4421db4" alt="RPi-4 with USB Drive" width="900" height="675" data-path="images/ota-rpi4-overall-conn.jpg" />

### Boot into Utility OS and Install Necessary Software

Power on the Raspberry Pi 4 device, and wait a couple of minutes for the OS to
boot up. Find the IP address of the device using a network scanning tool such as
[Nmap](https://nmap.org/). SSH onto the RPi-4: `ssh <username>@<rpi4-ip-addr>`.
Commands in the rest of this section are executed in the SSH shell on the RPi-4.

1. Keep the Raspberry Pi OS and packages up to date, and get the latest EEPROM
   firmware.

   ```bash theme={"dark"}
   thistle@rpi4-util:~ $ sudo apt update
   thistle@rpi4-util:~ $ sudo apt dist-upgrade -y
   thistle@rpi4-util:~ $ sudo rpi-eeprom-update -a
   ```

   <Note>
     Because of issues on the earlier versions of the Raspberry Pi 4, one needs to
     run on the latest EEPROM firmware.
   </Note>

2. Run `sudo raspi-config` to enable the Serial Port interfaces. The option is
   under "Interface Options" in the main menu. Click on "Finish" to save the
   configuration changes, and reboot the RPi-4 for the changes to take effect.

   <img src="https://mintcdn.com/thistletechnologies/MrBm0BC7xpW_ySdM/images/ota-raspi-config.png?fit=max&auto=format&n=MrBm0BC7xpW_ySdM&q=85&s=19d6b953c8c2791067cc14eb44cd48bb" alt="Run raspi-config" width="800" height="571" data-path="images/ota-raspi-config.png" />

3. Insert the microSD card after the utility OS has booted. We will customize
   the stock Raspberry Pi OS image to make it ready for the A/B update demo.

   On the Raspberry Pi in the utility OS, the microSD card should show up as
   `/dev/mmcblk0`. It contains two partitions: a boot partition `mmcblk0p1`, and
   a rootfs partition `mmcblk0p2`.

   ```bash theme={"dark"}
   thistle@rpi4-util:~ $ lsblk /dev/mmcblk0
   NAME        MAJ:MIN RM  SIZE RO TYPE MOUNTPOINTS
   mmcblk0     179:0    0 58.9G  0 disk
   ├─mmcblk0p1 179:1    0  512M  0 part
   └─mmcblk0p2 179:2    0  2.1G  0 part
   ```

   For A/B update to work, we need a third partition for the alternative rootfs,
   and will create it using `fdisk` on the utility OS interactively.

   ```bash theme={"dark"}
   thistle@rpi4-util:~ $ sudo fdisk /dev/mmcblk0
   ```

   In the `fdisk` menu, first show the current partition table. Take note on the
   end sector number for `/dev/mmcblk0p2` - we will create a new partition right
   after it.

   ```bash theme={"dark"}
   Welcome to fdisk (util-linux 2.38.1).
   Changes will remain in memory only, until you decide to write them.
   Be careful before using the write command.

   Command (m for help): p
   Disk /dev/mmcblk0: 58.94 GiB, 63281561600 bytes, 123596800 sectors
   Units: sectors of 1 * 512 = 512 bytes
   Sector size (logical/physical): 512 bytes / 512 bytes
   I/O size (minimum/optimal): 512 bytes / 512 bytes
   Disklabel type: dos
   Disk identifier: 0xd9c86127

   Device         Boot   Start     End Sectors  Size Id Type
   /dev/mmcblk0p1        16384 1064959 1048576  512M  c W95 FAT32 (LBA)
   /dev/mmcblk0p2      1064960 5390335 4325376  2.1G 83 Linux
   ```

   Now create a new primary partition `/dev/mmcblk0p3`, as follows. Set the
   first sector value the last sector number of `/dev/mmcblk0p2` plus 1 (5390336
   in this example), and use the default value for all other choices.

   ```bash theme={"dark"}
   Command (m for help): n
   Partition type
     p   primary (2 primary, 0 extended, 2 free)
     e   extended (container for logical partitions)
   Select (default p): p
   Partition number (3,4, default 3): 3
   First sector (2048-123596799, default 2048): 5390336
   Last sector, +/-sectors or +/-size{K,M,G,T,P} (5390336-123596799, default 123596799): 123596799

   Created a new partition 3 of type 'Linux' and of size 56.4 GiB.

   Command (m for help): w
   The partition table has been altered.
   Calling ioctl() to re-read partition table.
   Syncing disks.
   ```

   Check the block device information again to make sure the new partition has
   been created.

   ```bash theme={"dark"}
   thistle@rpi4-util:~ $ lsblk /dev/mmcblk0
   NAME        MAJ:MIN RM  SIZE RO TYPE MOUNTPOINTS
   mmcblk0     179:0    0 58.9G  0 disk
   ├─mmcblk0p1 179:1    0  512M  0 part
   ├─mmcblk0p2 179:2    0  2.1G  0 part
   └─mmcblk0p3 179:3    0 56.4G  0 part
   ```

4. We also need to customize the file system table (the `fstab` file) of the
   microSD card's default rootfs, and add the `tuc` binary to the rootfs.

   * Mount the default rootfs partition.

     ```bash theme={"dark"}
     thistle@rpi4-util:~ $ mkdir mymount
     thistle@rpi4-util:~ $ sudo mount /dev/mmcblk0p2 mymount/
     ```

   * Modify `/etc/fstab`

     ```bash theme={"dark"}
     thistle@rpi4-util:~ $ sudo nano mymount/etc/fstab
     ```

     Find the line

     ```text theme={"dark"}
     PARTUUID=...  /boot/firmware  vfat    defaults          0       2
     ```

     Change the second column from `/boot/firmware` to `/boot`. Save and exit.

   * Add the latest TUC binary to `/usr/bin/`

     ```bash theme={"dark"}
     thistle@rpi4-util:~ $ wget https://downloads.thistle.tech/embedded-client/1.5.0/tuc-1.5.0-aarch64-unknown-linux-musl.gz
     thistle@rpi4-util:~ $ gunzip tuc-1.5.0-aarch64-unknown-linux-musl.gz
     thistle@rpi4-util:~ $ chmod +x tuc-1.5.0-aarch64-unknown-linux-musl
     thistle@rpi4-util:~ $ sudo cp tuc-1.5.0-aarch64-unknown-linux-musl mymount/usr/bin/tuc
     ```

   * Sync the changes made to the microSD card, and umount.

     ```bash theme={"dark"}
     thistle@rpi4-util:~ $ sync
     thistle@rpi4-util:~ $ sudo umount mymount
     ```

   * For convenience of the demo, enable UART in the demo OS by modifying
     `config.txt` in the boot partition.

     ```bash theme={"dark"}
     thistle@rpi4-util:~ $ sudo mount /dev/mmcblk0p1 mymount/
     thistle@rpi4-util:~ $ sudo nano mymount/config.txt
     ```

     Add `enable_uart=1` to the last line of `config.txt`. Save the change, and
     power off the Raspberry Pi.

     ```bash theme={"dark"}
     thistle@rpi4-util:~ $ sync
     thistle@rpi4-util:~ $ sudo umount mymount
     thistle@rpi4-util:~ $ sudo poweroff
     ```

   Now the microSD card is ready for the A/B update demo.

## OTA A/B Update Demo

When the RPi-4 is powered off, unplug the USB drive, and leave the microSD card
inserted. On PC, connect to the serial port so you can start watching boot logs.

```bash theme={"dark"}
# This command runs on a Linux PC. Change /dev/ttyUSB0 to your serial port if
# needed.
$ minicom -b 115200 -D /dev/ttyUSB0
```

Now power on the RPi-4 board. This time it boots from the microSD card. You
should see boot logs from the serial console, and eventually the login prompt.
Verify that you can login with the username and password you set when
programming the microSD card.

```bash theme={"dark"}
thistle@ota-demo:~ $ uname -a
Linux ota-demo 6.12.25+rpt-rpi-v8 #1 SMP PREEMPT Debian 1:6.12.25-1+rpt1 (2025-04-30) aarch64 GNU/Linux
```

### Create an update bundle on PC

For the purpose of this example, we will release the [alternative rootfs
image](https://downloads.thistle.tech/rpi/four-v1.5.0-updated.rootfs) as an
update.  This image will be deployed with the Release Helper, and will be
installed on the target automatically by the Update Client.

First download the rootfs update image to PC.

```bash theme={"dark"}
$ curl -O https://downloads.thistle.tech/rpi/four-v1.5.0-updated.rootfs
```

Let's start with packaging the update:

```bash theme={"dark"}
# Set up your project's access token - Bash, on Linux/Unix or Windows Subsystem for Linux (WSL)
$ export THISTLE_TOKEN=$(cat)
(paste access token, press enter, then ctrl-d)

# Set up your project's access token - Windows PowerShell
$ $env:THISTLE_TOKEN = "[Access Token Obtained from Thistle App's Projects Section]"

$ ./trh --signing-method="remote" init -persist="/boot"

# prepare manifest and deploy partition image
$ ./trh --signing-method="remote" prepare --target=./four-v1.5.0-updated.rootfs

$ ./trh --signing-method="remote" release
```

<Note>
  The command line option `--signing-method="remote"` uses a Thistle-managed,
  [Cloud
  KMS](https://cloud.google.com/security/products/security-key-management)-backed
  ECDSA-P256 key to sign OTA update bundles. For other signing methods supported
  by TRH, please refer to our blog post [OTA Bundle Signing in Production with
  Thistle Release
  Helper](https://thistle.tech/blog/signing-releases-with-thistle-release-helper).
</Note>

### Amend device configuration for bootloader configuration

To support A/B root filesystem updates with the Thistle Update Client, it is required to amend the configuration, and add the two partitions used for A/B updates, as well as the two scripts used by the Update Client to handle bootloader interaction. Refer to the [Bootloader Integration](/update/bootloaders/overview) page for more information on these scripts.

```bash theme={"dark"}
$ cat config.json
{
    ...
    "switch_part_command": "/opt/switch-part-update.sh",
    "lock_part_command": "/opt/lock-update.sh",
    "part_a": "/dev/mmcblk0p2",
    "part_b": "/dev/mmcblk0p3"
}
```

```bash theme={"dark"}
$ cat switch-part-update.sh
#!/bin/bash
TARGET_PART=$1
cd /boot/firmware

# amend cmdline to switch root partition
sudo cp cmdline.txt try_cmdline.txt
sudo sed -i "s|root=[^ ]*|root=${TARGET_PART}|" try_cmdline.txt

# set set try_cmdline in tryboot.txt config
sudo cp config.txt tryboot.txt
if grep -q "^cmdline=" tryboot.txt; then
  sudo sed -i 's/^cmdline=.*/cmdline=try_cmdline.txt/' tryboot.txt
else
  echo "cmdline=try_cmdline.txt" >> tryboot.txt
fi

sudo reboot "0 tryboot"
```

```bash theme={"dark"}
#!/bin/bash
cd /boot/firmware
sudo rm tryboot.txt
sudo cp try_cmdline.txt cmdline.txt
exit 0
```

It is now required to copy `config.json` onto the Raspberry Pi, this can be done
using `scp` or using a USB key. This is necessary so that we can run the update
client on the Raspberry Pi with the appropriate configuration. Under normal
circumstances, this operation will be performed automatically during e.g. the
image personalization step of a deploy pipeline.

```bash theme={"dark"}
# on the PC (host machine) - you likely need to amend the IP address
scp config.json thistle@192.168.1.102:~/tuc-config.json
```

<Note>
  We are now ready to run the client on the Raspberry Pi, and observe our first update cycle.
</Note>

### Test deployed release

We can now test our deployed release. Note that as the assets will be downloaded
from the Thistle Backend Server, an internet connection is required.

```bash theme={"dark"}
# on the Raspberry Pi - confirm tuc-config.json file is present
thistle@ota-demo:~$ cat ~/tuc-config.json

# `mmcblk0p2` is currently used as mountpoint for root
thistle@ota-demo:~$ mount | grep /dev/mmc
/dev/mmcblk0p2 on / type ext4 (rw,relatime)

# we copy `tuc-config.json` to the /boot directory so that it's persistent
# across updates
thistle@ota-demo:~$ sudo cp ~/tuc-config.json /boot
```

Let's apply the update and see the result.

```bash theme={"dark"}
thistle@ota-demo:~$ sudo tuc -c /boot/tuc-config.json
```

The Raspberry Pi will now reboot. Login again and confirm that the appropriate
partition is now the root partition.

<Note>
  The default username and password for this alternative rootfs image are
  `thistle` and `raspberry`, respectively.
</Note>

```bash theme={"dark"}
# `mmcblk0p3` is now used as mountpoint for root
raspberrypi4-64-thistle:~$ mount | grep /dev/mmc
/dev/mmcblk0p3 on / type ext4 (rw,relatime)

# this marker file should appear in /home/thistle with the expected content
raspberrypi4-64-thistle:~$ cat message_from_thistle.txt
If you can see this message, your root filesystem has been successfully OTA
updated.

# latch in update by re-running tuc
raspberrypi4-64-thistle:~$ sudo tuc -c /boot/tuc-config.json
```

<Note>
  You just performed your first automated A/B update cycle 🎉 <a href="https://app.thistle.tech/projects">Have a look at the Thistle App</a>,
  your device can now be monitored directly there.
</Note>

## More options

This tutorial shows how to manually perform an update on the Raspberry Pi. This
process can be automated to update a fleet of devices. In this regards, we
support different enrollment setups, either based on Trust On First Use (TOFU),
or only accepting pre-enrolled devices.

* [Support for pre & post install scripts](/update/manifest#manifest-fields)
* [Incremental updates via file updates](/update/get_started/file_update)
* OTA bundle signing using external signing tools
  * [With YubiKey-protected signing key](https://github.com/thistletech/trh-y)
  * [With GCP-KMS-backed signing key](https://github.com/thistletech/trh-k)
