Skip to content

How to configure an odio node from the settings page

Since 2026.9.0b1, odio serves a settings page, at /ui/admin/ on the embedded web UI since 2026.10.0b1, on port 8021 before. Behind it is odioctl. It replaces the former odio-upgrade.py script, and it is meant to grow as needs come. For now it covers applying upgrades, enabling or disabling components, selecting the audio HAT and rebooting after one, and the Qobuz Connect and Tidal logins. What gets pulled into it versus left on the command line follows the configuration philosophy: bring your case to discussions.

http://<ip>:8018/ui/admin/

Or via Zeroconf (mDNS):

http://<hostname>.local:8018/ui/admin/

The gear icon in the header of the embedded web UI opens it. odio-api proxies the page from a Unix socket, $XDG_RUNTIME_DIR/odioctl-web.sock, which odios sets as its api.ui.adminSocket key; odioctl itself listens on no port.

There is no authentication, the same LAN trust model as odio-api: anyone who can reach the API can act on the node. Every form carries a per-process token, so a page on another site cannot drive the node through your browser. To narrow who reaches it, narrow odio-api’s bind.

The page follows the node live: every section subscribes to an event stream and re-renders itself, so a toggle, an upgrade run or an audio HAT change shows up on every open page without a reload.

The full settings page of an odio node: the Upgrade section reporting up to date, the Components section listing roles and features grouped as Audio, Playback, Streaming and System, each row ending in a check mark or a lock, with Log in to Qobuz under Qobuz Connect and Qobuz and Log in to Tidal under Tidal, and the Audio HAT section with the HiFiBerry DAC+ standard picked, a line reading current: hifiberry-dacplus-std, managed by odioctl, and a hint that USB DACs are picked as the default sink

The top section mirrors the daily check run by odio-check-upgrade.timer: no check yet, up to date, or the list of roles with a newer version in the target release. Apply now starts odio-upgrade.service, the same unit Home Assistant and the embedded web UI trigger, so progress shows up in odio-ui and in the Home Assistant Firmware entity as usual. The node keeps playing, but services restart along the way. While the run is in flight the button reads Upgrading…, and how it ended stays under the section afterwards, done or failed with its exit code. See How to upgrade odio for the full flow.

Components are the roles odios installs (Bluetooth, MPD, CD player, AirPlay, Spotify Connect, Qobuz Connect, Snapcast, UPnP / DLNA, odio-api, Branding, the local screen) and the features that plug into a role (myMPD under MPD, Tidal, Qobuz and Web radios under UPnP / DLNA). They are grouped as Audio, Playback, Streaming and System. The Audio server row heading the list is a choice rather than a toggle: it offers a server to switch to once the release has one, and is locked otherwise.

Each row ends in one icon for its state, and hovering it shows what a click does:

IconStateClick
CheckInstalledDisable
Crossed circleDisabledEnable
Arrow up, pulsingInstalls on the next upgradeSkip
Arrow down, pulsingRemoved on the next upgradeKeep
LockRequired by odio—

A click only records the choice in /var/lib/odio/state.json. Nothing changes on the node until the next upgrade run: an enabled component is installed then, a disabled one is turned off, its services stopped and disabled, its packages left in place. Trigger an upgrade to apply right away.

Tidal, Qobuz and Qobuz Connect carry a login button on their row, once the component is installed. It runs the sign-in helper on the node and opens its output in a dialog, with a link to complete the login from your browser. The row then carries a Done chip, except for Qobuz, whose sign-in ends on upmpdcli’s own page: its link stays on the row. See the Tidal and Qobuz Connect guides for what comes after.

This section needs a config.txt; on anything but a Raspberry Pi it says so and offers nothing. Pick the board from the list and click Apply, which stays greyed out until one is picked. odioctl writes one marked block at the top of /boot/firmware/config.txt, ahead of the file’s own overlays:

# BEGIN odioctl dac -- managed block, edit with `odioctl dac`
[all]
dtoverlay=
dtparam=audio=off
dtoverlay=hifiberry-dacplus-std
# END odioctl dac

Since 2026.10.0b2, the empty dtoverlay= stops the firmware from loading the overlay a HAT’s EEPROM names, so the board picked here is the only one loaded. dtparam=audio=off turns the Pi’s own audio off. Any audio line odioctl recognizes elsewhere in the file is commented out with an #odioctl-disabled: prefix. A copy of the original is kept once as config.txt.odioctl.bak. The change takes effect after a reboot: odioctl leaves a /run/odioctl/reboot-required flag, and the page carries a banner with a Reboot now button, which confirms first, until the node comes back.

Reset removes the block and restores the commented lines: that is also how to go back to the Pi’s own audio (3.5mm jack / HDMI), which is not an entry of the list. A block odioctl 0.4.0 wrote for onboard audio is still read, and the page offers Reset for it. An overlay odioctl does not know is left untouched, whether a line is audio is not something to guess at.

USB DACs need none of this: pick one under Audio Server › Default Sink in the web UI.

Overclocking stays a manual edit, outside the managed block, see Installation.

The Audio HAT section of the settings page with the board list dropped down, showing a pick an audio HAT placeholder, two generic I2S entries and the HiFiBerry and IQaudIO ranges, each with its id in parentheses, above a line reading no audio HAT: the Pi's own audio

Supported boards

The id column is what odioctl dac set takes. Missing yours? Open an issue on odioctl with the dtoverlay line it needs.

IdBoard
i2s-dacGeneric passive I2S DAC (Pi as clock master)
i2s-master-dacGeneric I2S DAC acting as clock master
hifiberry-dacHiFiBerry DAC / DAC+ Light / DAC+ Zero / MiniAmp / PCM5102
hifiberry-dacplusHiFiBerry DAC+ (auto-detect std/pro)
hifiberry-dacplus-stdHiFiBerry DAC+ (standard)
hifiberry-dacplus-proHiFiBerry DAC+ Pro / DAC2 Pro
hifiberry-dacplusadcHiFiBerry DAC+ ADC
hifiberry-dacplusadcproHiFiBerry DAC+ ADC Pro / DAC2 ADC Pro
hifiberry-dacplushdHiFiBerry DAC+ HD / DAC2 HD
hifiberry-dacplusdspHiFiBerry DAC+ DSP
hifiberry-dac8xHiFiBerry DAC8x (Pi 5 only)
hifiberry-studio-dac8xHiFiBerry Studio DAC8x
hifiberry-studio-dac8x-proHiFiBerry Studio DAC8x Pro
hifiberry-digiHiFiBerry Digi / Digi+
hifiberry-digi-proHiFiBerry Digi+ Pro / Digi2 Pro
hifiberry-studio-digiHiFiBerry Studio Digi / AES
hifiberry-ampHiFiBerry Amp / Amp+
hifiberry-amp100HiFiBerry Amp100
hifiberry-amp3HiFiBerry Amp3
hifiberry-amp4proHiFiBerry Amp4 Pro
iqaudio-dacIQaudIO Pi-DAC / Pi-DAC Zero
iqaudio-dacplusIQaudIO Pi-DAC+ / Pi-DAC Pro / Pi-DigiAMP+
iqaudio-digi-wm8804-audioIQaudIO Pi-Digi+
iqaudio-codecIQaudIO Pi-Codec+ / Codec Zero
akkordion-iqdacplusDigital Dreamtime Akkordion (IQaudIO DAC+ based)
rpi-dacplusRaspberry Pi DAC+
rpi-dacproRaspberry Pi DAC Pro
rpi-digiampplusRaspberry Pi DigiAMP+
rpi-codeczeroRaspberry Pi Codec Zero
allo-boss-dac-pcm512x-audioAllo Boss DAC
allo-boss2-dac-audioAllo Boss2 DAC
allo-piano-dac-pcm512x-audioAllo Piano DAC 2.0 (2.1 in stereo only)
allo-piano-dac-plus-pcm512x-audioAllo Piano DAC 2.1
allo-digioneAllo DigiOne
allo-katana-dac-audioAllo Katana DAC
justboom-dacJustBoom DAC HAT / Amp HAT / DAC Zero / Amp Zero
justboom-digiJustBoom Digi HAT / Digi Zero
justboom-bothJustBoom DAC + Digi (stacked)
pifi-dac-hdPiFi DAC HD
pifi-dac-zeroPiFi DAC Zero
pifi-40PiFi 40W stereo amplifier
pifi-mini-210PiFi Mini stereo amplifier
dionaudio-locoDion Audio LOCO DAC-AMP
dionaudio-loco-v2Dion Audio LOCO-V2 DAC-AMP
dionaudio-kiwiDion Audio KIWI Streamer
audioinjector-wm8731-audioAudioInjector Zero / Stereo
audioinjector-addonsAudioInjector Octo
audioinjector-ultraAudioInjector Ultra
audioinjector-isolated-soundcardAudioInjector Isolated
audioinjector-bare-i2sAudioInjector bare I2S
pisoundBlokas Pisound
pisound-pi5Blokas Pisound (Pi 5)
pisound-microBlokas Pisound Micro
applepi-dacOrchard Audio ApplePi-DAC
i-sabre-q2mAudiophonics I-Sabre Q2M
rra-digidac1-wm8741-audioRed Rocks Audio DigiDAC1
dacberry400DACBerry 400
chipdip-dacChip Dip DAC
interludeaudio-analogInterlude Audio Analog HAT

Everything on the page is a subcommand of odioctl, run as the odio user over SSH. The privileged ones, upgrade apply and dac set / dac unset, go through sudo, which the package’s sudoers fragment allows without a password for members of the odioctl group.

Terminal window
odioctl upgrade check # compare state.json with the published manifest
sudo odioctl upgrade apply # what Apply now and odio-upgrade.service run
odioctl components list # roles and features with their status
odioctl components disable branding # recorded in state.json, applied on the next upgrade
odioctl dac list # the board catalog
sudo odioctl dac set hifiberry-dacplus-std # then reboot
sudo odioctl dac unset # back to the Pi's own audio, then reboot

To track an odios pre-release instead of the published latest, pass --version pr-84 to upgrade check and upgrade apply, or set ODIOCTL_ODIOS_VERSION=pr-84 in /etc/default/odioctl, which both the daily timer and the settings page read. Only a release tag is accepted there, never a URL: it is interpolated into a GitHub release path for odios and nothing else. See CI/CD for how PRs get published as pr-<N> pre-releases.

Full synopsis and exit codes
odioctl upgrade check [--version TAG] [--state PATH] [--output PATH]
odioctl upgrade apply [--version V] [--state PATH] [--dry-run] [--force] [--reinstall] [--progress|--no-progress]
odioctl upgrade verify [--state PATH] [--expected-version TAG]
odioctl pwa-url
odioctl components [--state PATH] list [--json] | enable NAME | disable NAME
odioctl dac list [--json] | status [--json] | set ID [--dry-run] | unset
odioctl web [--bind 0.0.0.0] [--port 8021] [--socket PATH] [--systemd-only] [--ui-url URL] [--state PATH] [--config PATH]
CommandExit codes
upgrade check0 up to date, 1 upgrades available, 2 error
upgrade apply0 upgraded or nothing to do, 1 install.sh failed, 2 error
upgrade verify0 valid, 1 invalid, 2 state.json missing

apply re-runs install.sh from the target release with the opt-outs derived from state.json, and skips roles whose version did not move. --reinstall re-runs every role in full. Only the current state.json schema is accepted, nodes installed before 2026.5 need a fresh install.

Under the hood
  • What the package ships. The odioctl binary, user units for the daily check, the upgrade run and the settings page, and /etc/sudoers.d/odioctl. odios enables odio-check-upgrade.timer and odioctl-web-proxy.socket for the target user. The socket holds odioctl-web.sock from boot and starts odioctl web on the first connection, so restarting the service never drops a request. odioctl-web.socket, port 8021, still ships: an upgrade from 2026.9 disables it, and enabling it again serves the page on that port too.
  • Who runs what. The page runs as the odio user. It edits state.json directly, which is why /var/lib/odio is group-writable by odio since 2026.9.0b1, and reaches config.txt through sudo -n odioctl dac ….
  • Two groups on purpose. odio grants access to state.json and holds the installing user too. odioctl grants passwordless root for exactly three things: upgrade apply --progress, dac set <id> with one sudoers line per catalog id, and dac unset. The fragment is generated from the DAC catalog, and CI fails when the two drift. A group that grants reads must not also grant root.
  • The cache. /var/cache/odio/upgrades.json is rewritten by the check and after every component toggle. odio-api and the SSH MOTD read it, see the upgrade API for its contract.