Skip to content

How to upgrade odio and check for releases

The simplest way to check for and apply upgrades is from Home Assistant or the embedded web UI, no SSH required. Both are backed by go-odio-api (v0.15.1), which exposes the odio-check-upgrade and odio-upgrade units, and /var/cache/odio/upgrades.json, over its API. A full odios install wires this up automatically since 2026.6.0b2.

In the embedded web UI, a status badge sits in the header:

  • a check mark when the node is up to date, click to re-check;
  • an up arrow when an upgrade is available, click to start it (with a confirmation);
  • a progress ring while a run is in flight;
  • a red alert badge when the last run failed, hover to reveal the up arrow and retry; it persists until the next run.

Embedded UI header badge for an odio node, showing a green up arrow that indicates an upgrade is available

In Home Assistant, the node carries a native Firmware update entity (update.odio_remote_<hostname>_firmware). It tracks the installed and latest versions, shows an Install button when the server allows starting an upgrade, and reports live progress during a run. Detection is server-driven over SSE, so HA never polls.

Home Assistant Firmware update entity for an odio node, showing the installed version, a newer available version, and Ignore and Update buttons

Both reflect whatever the node’s detector reports and trigger the same units, see the upgrade API for the contract behind them.

Each node shows its update status in the MOTD when you SSH in:

odio MOTD on SSH login, showing an update available from dev-b94c3fa3 to 2026.4.1rc1, with per-role deltas for branding, shairport_sync and upmpdcli

The notice lists the overall version bump and the roles whose version actually changed. If nothing changed, the notice is absent.

A systemd user timer (odio-check-upgrade.timer) reruns the check daily. Run it on demand to see the result on stdout:

odio@raspodio:~ $ odio-upgrade check
Upgrades available: dev-8e978895 → 2026.5.0b1

Or trigger the user unit, with the result landing in the user journal:

Terminal window
systemctl --user start odio-check-upgrade.service

Trigger the systemd user unit:

Terminal window
systemctl --user start odio-upgrade.service

It re-runs the installer with the feature selection from the previous run. Since 2026.5.0b1, it only re-runs the roles that actually changed in the target release (smart upgrade), bringing most upgrades down to a handful of roles.

For flags, call the script directly:

Terminal window
odio-upgrade apply # same as the unit
odio-upgrade apply --version 2026.5.0b1 # pin a release tag
odio-upgrade apply --dry-run --force # print the derived env, no changes

Open PRs are published as pr-<N> pre-releases (see CI/CD), so testing a PR before merge is the same call:

Terminal window
odio-upgrade apply --version pr-52

The installer is idempotent and safe to re-run. When a config file has been locally modified, it creates a <config>.bak before applying changes; if the file ends up identical, no backup is kept (Bluetooth, MPD, mpd-discplayer, odio-api, PipeWire, shairport-sync, spotifyd, upmpdcli).

Fresh install:

Terminal window
curl -fsSL https://beta.odio.love/install | bash

Pin a release tag:

Terminal window
curl -fsSL https://github.com/b0bbywan/odios/releases/download/2026.5.0b1/install.sh | ODIOS_VERSION=2026.5.0b1 bash

If the node was installed before 2026.4.2b1, it lacks the odio-upgrade helper. Each release ships it as a standalone asset, so it can run before being installed:

Terminal window
curl -fsSL https://odio.love/upgrade -o /tmp/odio-upgrade
chmod +x /tmp/odio-upgrade
/tmp/odio-upgrade

The next run finds the helper installed in /usr/local/bin and the state file at /var/lib/odio/state.json.

odio-upgrade is opt-out: only entries listed in state.json map to INSTALL_*=N. Everything else, including roles or features added in a later release, is installed.

Edit /var/lib/odio/state.json and add the entry to the matching _excluded list, for example to keep branding and upnpwebradios off:

"roles_excluded": [
"snapclient", "spotifyd"
"snapclient", "spotifyd", "branding"
],
"features_excluded": [
"tidal"
"tidal", "upnpwebradios"
]

Verify and apply:

Terminal window
odio-upgrade apply --dry-run --force # print the derived INSTALL_* flags
odio-upgrade apply

Removing an entry opts back in.

State files behind the upgrade flow

Three files back the detection and upgrade flow:

  • Local state/var/lib/odio/state.json, written by the installer after each successful run and read by odio-upgrade apply. Tracks the odios version, install mode, target user, per-role versions, opt-in features, explicit opt-outs, and a release_history of the last odios versions installed on this node. The file is 0640 root:odio:

    {
    "features": [
    "tidal",
    "qobuz",
    "upnpwebradios",
    "mympd"
    ],
    "features_excluded": [],
    "install_mode": "live",
    "odios": "2026.5.0b1",
    "release_history": [
    "2026.4.2b2",
    "2026.5.0b1"
    ],
    "roles": {
    "bluetooth": "2026.5.0b1",
    "branding": "2026.5.0b1",
    "common": "2026.5.0b1",
    "mpd": "2026.5.0b1",
    "mpd_discplayer": "2026.5.0b1",
    "odio_api": "2026.5.0b1",
    "pulseaudio": "2026.4.2b1",
    "shairport_sync": "2026.4.1rc1",
    "snapclient": "2026.4.0rc5",
    "spotifyd": "2026.5.0b1",
    "upgrade": "2026.5.0b1",
    "upmpdcli": "2026.4.2b2"
    },
    "roles_excluded": [],
    "target_user": "odio"
    }
  • Published manifestodio.love/manifest.json, generated by CI on each release with the same shape. Each role version is the last odios release that touched that role.

  • Check result/var/cache/odio/upgrades.json, written by odio-upgrade check (the daily timer’s job) and read by the MOTD:

    {
    "current": "2026.4.2b2",
    "latest": "2026.4.2b2",
    "upgrade_available": false,
    "roles": [],
    "manifest": {
    "odios": "2026.4.2b2",
    "roles": {
    "bluetooth": "2026.4.0rc5",
    "branding": "2026.4.2b1",
    "common": "2026.4.2b2",
    "mpd": "2026.4.2b2",
    "mpd_discplayer": "2026.4.2b1",
    "odio_api": "2026.4.2b2",
    "pipewire": "2026.4.0rc6",
    "pulseaudio": "2026.4.2b1",
    "shairport_sync": "2026.4.1rc1",
    "snapclient": "2026.4.0rc5",
    "spotifyd": "2026.4.2b1",
    "upgrade": "2026.4.2b2",
    "upmpdcli": "2026.4.2b2"
    }
    },
    "checked_at": "2026-05-07T19:32:34Z"
    }