Skip to content

Upgrade

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"
    }