Skip to content

Updating the Pi

OneSeventyFour edited this page Jun 19, 2026 · 2 revisions

Updating the Pi

There are four things that can drift on a Pi install:

  1. Host-side source codeinstall.sh, start.sh, update.sh, the AP scripts, configs, anything under host/run/pi/ and host/tcp_serial_bridge/. Lives in the git checkout at /opt/backyardhero/.
  2. The Docker app imageos4ivmb/backyardhero:latest. This is where the actual Next.js app, daemon, and websocket server live in production. Pulled from Docker Hub.
  3. System-level state — the systemd units, udev rules, hostapd / dnsmasq config, NAT rules. Created by the installer.
  4. The running stack — the live byh-host.service process, which won't pick up new code / image / system state until it's restarted.

host/run/pi/update.sh is the one-button script that does all four in the right order. Most of the time you just run it and walk away.


TL;DR

sudo /opt/backyardhero/host/run/pi/update.sh

That runs, in order:

  1. git pull --ff-only in /opt/backyardhero (source).
  2. docker compose -f host/run/pi/docker-compose.yml pull (image).
  3. install.sh -y (re-apply system state — idempotent).
  4. systemctl restart byh-host (live stack picks up everything).

Pass -y to skip the confirmation prompt. The script prints what it's about to do up-front and asks "Continue? [Y/n]".


Selective flags

For the times you know one of the four things didn't change, skip it:

--no-source    Skip git pull (use what's already on disk).
--no-image     Skip docker compose pull (keep current image).
--no-install   Skip re-running install.sh.
--no-restart   Leave the service alone; new image queues for next boot.
--reboot       Replace step 4 with a full reboot (use after udev / systemd
               changes if you're not sure the restart picks them up).
--branch X     Switch to branch X before pulling.
-y, --yes      Non-interactive.

Common combinations

Pull a newer container image only (host scripts and system state are fine — most common scenario after a Docker Hub release):

sudo /opt/backyardhero/host/run/pi/update.sh --no-source --no-install

Pull host scripts only (you've edited install.sh or start.sh upstream but the app image is fine):

sudo /opt/backyardhero/host/run/pi/update.sh --no-image

Pull from a feature branch and rebuild everything:

sudo /opt/backyardhero/host/run/pi/update.sh --branch experiment/my-thing

Full reboot after a system-state change (new systemd units, new udev rules):

sudo /opt/backyardhero/host/run/pi/update.sh --reboot

What each step actually does

Step 1 — git pull --ff-only

Runs in /opt/backyardhero. Bails if your working tree has local edits that can't fast-forward (e.g. you've been editing install.sh on the Pi itself). If that happens, either commit / stash the changes or pass --no-source.

Sources for "what changes here matter":

  • host/run/pi/install.sh — system-level state. Will be re-run in step 3.
  • host/run/pi/start.sh — the systemd unit's ExecStart. Step 4 picks it up.
  • host/run/pi/docker-compose.yml — what the stack actually mounts and exposes. Step 4 picks it up.
  • host/tcp_serial_bridge/ — the host-native bridge. Step 4 picks it up.
  • host/byh_app/, host/pythings/ — these are baked into the Docker image in prod mode, so source changes here only matter in dev (start-dev.sh). Step 2 is what picks them up in prod.

Step 2 — docker compose pull

Pulls os4ivmb/backyardhero:latest (or whatever tag the compose file specifies). If the Pi has no upstream internet at the moment (AP-only deployment), the script warns and continues using the cached image. Useful when you want a quick re-apply without an internet detour.

Step 3 — install.sh -y

Re-runs the installer non-interactively. Every step is idempotent so this is safe to run repeatedly — at worst it rewrites a few files that already say the right thing. It picks up:

  • New systemd units committed in the repo.
  • Changes to hostapd.conf / dnsmasq.conf defaults.
  • New udev rules (but you may need --reboot for udev to act on a brand-new rule mid-running).
  • Newly required apt packages (the installer's apt install -y line).

Pass --no-install if you know nothing system-level changed and want a faster cycle. Skipping it after a git pull that did move install.sh is the most common cause of "the new feature isn't showing up" head-scratching.

Step 4 — systemctl restart byh-host

Bounces the actual stack. This is what swaps the running container from the old image to the new one. The bridge restarts too — you'll see a brief gap in journalctl -u byh-host -f and the dongle will reconnect within a second or two.

If you'd rather defer the restart to the next boot (e.g. you're mid-show and don't want to risk a hiccup), pass --no-restart. The new image will be used the next time byh-host starts.


Updating from the UI

If you're connected to the Pi's AP and don't want to drop into SSH, the same update can be triggered from the web UI:

Settings → Network → System update → Check for updates → Apply update.

Under the hood this is the same byh-update pipeline as the AP-apply flow:

Browser  ─────► POST /api/system/update  (Next.js, in container)
                  │
                  │  Writes payload to /data/byh_update_request.json
                  ▼
              systemd path watcher  (byh-update.path)
                  │
                  ▼
              byh-update.py  (root, on the host)
                  │  - parses request (--no-source / --no-image / etc.)
                  │  - shells out to /opt/backyardhero/host/run/pi/update.sh
                  │  - streams stdout/stderr to /data/byh_update_status.json
                  ▼
              Next.js polls /data/byh_update_status.json
                  │
              UI renders progress (current phase, last log line)
                  ▼
              When step 4 restarts byh-host, the UI's WS / API
              connections drop briefly and reconnect to the new
              container automatically.

The UI version is just update.sh -y with no extra flags. For surgical updates (skip image, override branch, etc.), SSH in and run the script directly.


Updating dongle firmware on the Pi

The Pi can also flash its plugged-in dongle from a fresh git checkout:

sudo /opt/backyardhero/host/run/pi/update_dongle.sh

That script (also under host/run/pi/) does:

  1. git pull in the repo (for the latest firmware source).
  2. devices/utils/build_dongle.sh to compile the new .bin.
  3. devices/utils/flash_dongle.py to flash it via esptool over /dev/byh_dongle.

See Flashing a Dongle for the full background. You can also kick the flash directly from the UI (Settings → Debug → Dongle firmware update), which skips the rebuild and just uses whatever .bin is already in devices/os4_dongle/bin/.


Verifying the update worked

After running update.sh:

# Has the source changed?
cd /opt/backyardhero && git log --oneline -5

# Is the image fresh?
docker images --format '{{.Repository}}:{{.Tag}} {{.CreatedSince}}' \
  | grep backyardhero

# Did the service come back?
sudo systemctl status byh-host

# Watch live logs for the first 30 seconds (look for the daemon's
# "ready" line and the bridge's "Serial connected" line):
sudo journalctl -u byh-host -f

From the UI, the About dialog (Settings → About, when present) shows the current host_version from systemcfg.json and the running image digest.


Rolling back

You have three options, in order of preference:

1. Pin a previous Docker image. Find the previous tag at https://hub.docker.com/r/os4ivmb/backyardhero/tags, then:

BYH_IMAGE=os4ivmb/backyardhero:v0.07 \
  sudo systemctl restart byh-host

(Permanent: edit docker-compose.yml's image: line.)

2. Roll back the git checkout.

cd /opt/backyardhero
git log --oneline -10        # find the commit you want
git reset --hard <sha>
sudo /opt/backyardhero/host/run/pi/install.sh -y
sudo systemctl restart byh-host

This rolls back host scripts but doesn't roll back the Docker image. Combine with option 1 if both need to revert together.

3. Reinstall from scratch on a clean Pi OS. Heavy-handed, but the surest: flash a fresh Raspberry Pi OS (64-bit) card and re-run the Pi install script. Back up /opt/backyardhero/host/data/ and /opt/backyardhero/host/config/systemcfg.json first, then drop them back in after the reinstall.


Common problems

"git pull failed"

Either:

  • Working tree has local changes. Run git status to see what's edited. Either commit/stash, or pass --no-source.
  • No internet on the Pi. Check ping github.com. If you're on AP-only deployment, plug in ethernet or share an upstream WiFi connection.

"docker compose pull failed (offline?)"

Same — usually no upstream. The script warns and continues with the cached image, so this is non-fatal. Take the Pi to internet, run update.sh --no-source --no-install once, then it's good to go offline again.

"install.sh failed"

install.sh prints the failing step. The most common offenders:

  • apt operation failed because the package list is stale: sudo apt update first, then retry.
  • hostapd can't be reconfigured because something is holding wlan0: sudo systemctl restart NetworkManager and retry.

Service restarts but UI looks the same

You probably skipped --no-install and install.sh reverted a change you'd made by hand. The installer's byh_ap_current.json write, for example, will overwrite manual edits to that file. If you made local config edits, commit them upstream so the installer preserves them.


See also

Clone this wiki locally