> ## 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 5

> Root Filesystem A/B OTA Update on Raspberry Pi 5 with headless image prep (Raspberry Pi OS Bookworm)

<img src="https://mintcdn.com/thistletechnologies/MrBm0BC7xpW_ySdM/images/pi5.png?fit=max&auto=format&n=MrBm0BC7xpW_ySdM&q=85&s=cdd099bac0406f4a549b72ee879a8f07" alt="Raspberry Pi 5" width="930" height="702" data-path="images/pi5.png" />

This guide shows how to perform Thistle OTA on a Raspberry Pi 5. Start with a quick file‑update path (no A/B). Optionally, pre‑partition a Raspberry Pi OS image to add a `rootfsB` partition and pre‑enable headless SSH/Wi‑Fi before flashing.

<Note>
  Raspberry Pi 5 uses a Pi firmware boot chain loading from the FAT boot partition. For OTA A/B, set the bootloader in the Thistle config to `RaspberryPi`. Depending on the distro, the boot partition may be mounted at `/boot` or `/boot/firmware`.
</Note>

## Board setup (vendor/community docs)

* Official imaging: Raspberry Pi Imager (“Use custom” supports local images)
* General microSD flashing workflow: [Prepare microSD](https://sbc-community.org/docs/general_guides/prepare_sd_card/)

## Quick path: File updates (no A/B)

Use Thistle’s file‑update mode to ship signed files and app bundles without changing partitions.

1. Download tools

* Workstation (TRH):

```bash theme={"dark"}
VER=1.5.0
curl -LO https://downloads.thistle.tech/embedded-client/$VER/trh-$VER-x86_64-unknown-linux-musl.gz
gunzip trh-$VER-x86_64-unknown-linux-musl.gz
chmod +x trh-$VER-x86_64-unknown-linux-musl && ln -sf trh-$VER-x86_64-unknown-linux-musl trh
```

* Device (TUC on RPi5, aarch64):

```bash theme={"dark"}
# On the device (SSH or serial)
VER=1.5.0
curl -LO https://downloads.thistle.tech/embedded-client/$VER/tuc-$VER-aarch64-unknown-linux-musl.gz
gunzip tuc-$VER-aarch64-unknown-linux-musl.gz
chmod +x tuc-$VER-aarch64-unknown-linux-musl
sudo mv tuc-$VER-aarch64-unknown-linux-musl /usr/local/bin/tuc
```

2. Initialize and release a simple file update on the workstation

```bash theme={"dark"}
export THISTLE_TOKEN=$(cat)   # paste token, Enter, then Ctrl-D
./trh init --persist="/boot"

mkdir -p example && echo "hello from thistle" > example/app
./trh prepare --target=./example --file-base-path=/opt/example
./trh release
```

3. Copy config to device and run client

```bash theme={"dark"}
# On workstation — change host/user to what you configured when imaging
scp config.json <user>@<rpi-host-or-ip>:/tmp/tuc-config.json

# On device
sudo mv /tmp/tuc-config.json /boot/tuc-config.json
sudo tuc -c /boot/tuc-config.json
```

That’s it! File updates are fetched, verified, and installed atomically. Continue below only if you need rootfs A/B for boot‑time rollback.

## Optional: Headless image prep (Raspberry Pi OS Bookworm, 64‑bit)

Prepare a Raspberry Pi OS image before flashing: create `rootfsB`, enable SSH, preconfigure Wi‑Fi, and set hostname. Docker is optional; any live Linux works.

### 1. Download the image

```bash theme={"dark"}
mkdir -p ~/rpi-img && cd ~/rpi-img
# Example: Raspberry Pi OS Lite (64‑bit) .img(.xz). Decompress if needed
# Use Raspberry Pi Imager or download a current Bookworm Lite image manually
# and set rpi5.img accordingly.
# e.g., with a downloaded file named 2025-05-13-raspios-bookworm-arm64-lite.img.xz
curl -LO https://downloads.raspberrypi.com/raspios_lite_arm64/images/2025-05-13/2025-05-13-raspios-bookworm-arm64-lite.img.xz
xz -d 2025-05-13-raspios-bookworm-arm64-lite.img.xz
mv 2025-05-13-raspios-bookworm-arm64-lite.img rpi5.img
```

### 2. Start a Linux environment in Docker (optional)

```bash theme={"dark"}
docker run --rm -it --privileged -v "$PWD":/work ubuntu:24.04 bash
```

Inside the container:

```bash theme={"dark"}
apt-get update
apt-get install -y parted e2fsprogs kpartx util-linux
cd /work
```

### 3. Expand image size & map partitions

```bash theme={"dark"}
truncate -s +8G rpi5.img                      # add 8 GiB free space
losetup -Pf --show rpi5.img                   # e.g. /dev/loop0
kpartx -av /dev/loop0                         # maps /dev/mapper/loop0p1, loop0p2
```

### 4. Resize rootfs (p2) to 6 GiB (example)

```bash theme={"dark"}
e2fsck -f /dev/mapper/loop0p2
resize2fs /dev/mapper/loop0p2 5900M   # adjust larger if your image requires more space
parted -s /dev/loop0 unit GiB resizepart 2 6
e2fsck -f /dev/mapper/loop0p2
resize2fs /dev/mapper/loop0p2
```

### 5. Create & format rootfsB

```bash theme={"dark"}
parted -s /dev/loop0 -a optimal mkpart primary ext4 6GiB 100%
kpartx -a /dev/loop0
mkfs.ext4 -F -L rootfsB /dev/mapper/loop0p3
```

### 6. Enable headless SSH + Wi‑Fi

Mount rootfs and boot partitions:

```bash theme={"dark"}
mkdir -p /mnt/root /mnt/boot
mount /dev/mapper/loop0p2 /mnt/root
mount /dev/mapper/loop0p1 /mnt/boot
```

#### a) Enable SSH at boot (RPi OS mechanism)

```bash theme={"dark"}
# An empty file named 'ssh' in the boot (FAT) partition enables SSH on first boot
: > /mnt/boot/ssh
```

#### b) Configure Wi‑Fi (wpa\_supplicant via boot)

```bash theme={"dark"}
cat > /mnt/boot/wpa_supplicant.conf <<'EOF'
country=US
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1

network={
    ssid="YOUR_SSID"
    psk="YOUR_PASSWORD"
}
EOF
```

#### c) (Optional) Pre‑create a user for headless login

On recent Raspberry Pi OS releases, a user is required. Either use Raspberry Pi Imager’s customization to set username/password, or add `userconf.txt` to the boot partition with a SHA‑512 hashed password:

```bash theme={"dark"}
# Replace 'pi' and the hash accordingly. Generate hash with: echo 'mypassword' | openssl passwd -6 -stdin
echo 'pi:$6$REDACTED_HASH' > /mnt/boot/userconf.txt
```

#### d) Set hostname

```bash theme={"dark"}
echo "rpi5" | tee /mnt/root/etc/hostname >/dev/null
# Ensure /etc/hosts maps 127.0.1.1 to the hostname
sed -i 's/^127.0.1.1.*/127.0.1.1\trpi5/' /mnt/root/etc/hosts || echo "127.0.1.1\trpi5" >> /mnt/root/etc/hosts
```

Unmount and clean up:

```bash theme={"dark"}
umount /mnt/boot /mnt/root
kpartx -dv /dev/loop0
losetup -d /dev/loop0
exit
```

### 7. Flash to SD

* Using Raspberry Pi Imager: select “Use custom” and choose `rpi5.img`.
* Or on macOS:

```bash theme={"dark"}
diskutil list                         # locate your SD (e.g., /dev/disk3)
diskutil unmountDisk /dev/diskN       # replace diskN with your SD identifier
sudo dd if=rpi5.img of=/dev/rdiskN bs=4m status=progress
sync
```

### 8. First boot

* Partitions:
  * p1 → boot (FAT)
  * p2 → rootfs (6 GiB ext4)
  * p3 → rootfsB (ext4, label `rootfsB`)
* SSH enabled automatically if `ssh` file present
* Wi‑Fi auto‑connects if `wpa_supplicant.conf` was provided
* Hostname set to `rpi5` (unless changed)

Login:

```bash theme={"dark"}
ssh <user>@rpi5.local
# or: ssh <user>@<rpi-ip>
```

## Thistle Tools

On the workstation and the device, download TRH/TUC as appropriate, then export your Project Access Token on the workstation.

```bash theme={"dark"}
# Workstation: TRH (prepare/release)
VER=1.5.0
curl -LO https://downloads.thistle.tech/embedded-client/$VER/trh-$VER-x86_64-unknown-linux-musl.gz
gunzip trh-$VER-x86_64-unknown-linux-musl.gz
chmod +x trh-$VER-x86_64-unknown-linux-musl && ln -sf trh-$VER-x86_64-unknown-linux-musl trh

# Device: TUC (install)
# Use the user/host you configured; example uses mDNS hostname
# Note: Use the aarch64 binary for 64‑bit images. If you chose a 32‑bit image,
# use the armv7 binary instead.
ssh <user>@rpi5.local 'VER=1.5.0; curl -LO https://downloads.thistle.tech/embedded-client/$VER/tuc-$VER-aarch64-unknown-linux-musl.gz && gunzip tuc-$VER-aarch64-unknown-linux-musl.gz && chmod +x tuc-$VER-aarch64-unknown-linux-musl && sudo mv tuc-$VER-aarch64-unknown-linux-musl /usr/local/bin/tuc && tuc --help | head -n 3'

# Workstation: Project token for TRH
export THISTLE_TOKEN=$(cat)
```

## Initialize and Prepare Rootfs Release

```bash theme={"dark"}
./trh init --persist="/boot"
./trh prepare --target=myrootfs.img
./trh release
```

## Device Configuration

Create `tuc-config.json` (adjust paths if your boot is `/boot/firmware`):

```json theme={"dark"}
{
  "name": "rpi5",
  "persistent_directory": "/boot",
  "public_keys": ["<YOUR_PUBLIC_KEY>"],
  "bootloader": "RaspberryPi",
  "part_a": "/dev/mmcblk0p2",
  "part_b": "/dev/mmcblk0p3"
}
```

Copy configuration to the board and persist under `/boot`:

```bash theme={"dark"}
scp tuc-config.json <user>@rpi5.local:/tmp/
ssh <user>@rpi5.local 'sudo cp /tmp/tuc-config.json /boot/tuc-config.json'
```

## Run Update

```bash theme={"dark"}
ssh <user>@rpi5.local
sudo tuc -c /boot/tuc-config.json
```

The device installs to the inactive slot and reboots. After reboot, run the client again to latch‑in the update.

## Notes and Tips

* If your distro mounts the boot partition at `/boot/firmware`, point `persistent_directory` there.
* For headless provisioning on Bookworm, Raspberry Pi Imager customization is the simplest route (set SSH, Wi‑Fi, user, hostname). Manual `ssh` + `wpa_supplicant.conf` still works and is widely documented.
* For more background on headless setup nuances on Bookworm vs Bullseye, see community articles (e.g., first‑run mechanisms and `userconf.txt`).
