Skip to content

How to configure an odio node from the settings page

Since 2026.9.0b1, odio serves a settings page on port 8021. 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 DAC overlay 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>:8021

Or via Zeroconf (mDNS):

http://<hostname>.local:8021

The embedded web UI links to it from the gear icon in its header, since odio-api v0.17.0. odios sets the api.ui.admin key for that.

There is no authentication, the same LAN trust model as odio-api: anyone who can reach the port can act on the node. Every form carries a per-process token, so a page on another site cannot drive the box through your browser. The port is held by a systemd user socket, odioctl-web.socket, on all interfaces. To keep it on loopback, override its ListenStream with a drop-in.

The page follows the node live: every section subscribes to an event stream and re-renders itself, so a toggle, an upgrade run or a DAC 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 with the outcome of the last run under it, the Components section listing roles and features grouped as Audio, Playback, Streaming and System, each row carrying an Installed chip and a Disable button, with a Log in to Qobuz button under Qobuz Connect and a Log in to Tidal button under Tidal, and the DAC section with the board picker set to onboard audio

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 (PulseAudio, 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, each with a status chip: Installed, Disabled, or Will install on next upgrade when the role is not on this node yet.

The button on the row only records the choice in /var/lib/odio/state.json: Enable, Disable, or Skip on a component that would install on the next upgrade. Nothing is installed or removed until the next upgrade run: enabling installs the component then, disabling keeps it installed but stops updating it. Trigger an upgrade to apply right away. Base system and Upgrade are infrastructure and cannot be toggled.

Tidal 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 valid for five minutes to complete the login from your browser; the row then carries a Done chip. 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. odioctl writes one marked block at the end of /boot/firmware/config.txt, with the onboard audio turned off and the board’s dtoverlay line, and comments out any audio line it recognizes elsewhere in the file 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. An overlay odioctl does not know is left untouched and reported under the picker, whether a line is audio is not something to guess at.

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

The DAC section of the settings page with the board list dropped down, showing a not configured placeholder, the onboard audio entry, two generic I2S entries and the HiFiBerry and IQaudIO ranges, each with its id in parentheses, above the Apply and Reset buttons and a line reading current: onboard, managed by odioctl

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
onboardOnboard audio (3.5mm jack / HDMI)
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

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] [--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.socket for the target user. The socket holds port 8021 from boot and starts odioctl web on the first connection, so restarting the service never drops a request.
  • 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.