Skip to content

Configuration

The odio installer generates ~/.config/odio-api/config.yaml automatically. The configuration shown below documents all available options — you only need to edit the file if you want to customize the defaults.

For standalone installations, the config file can also be specified with --config <path>.

bind: lo
logLevel: info
api:
enabled: true
port: 8018
ui:
enabled: true
cors:
origins: ["https://pwa.odio.love", "https://odio-pwa.vercel.app"]

The api block configures the HTTP server itself: port, embedded UI, SSE stream, and browser CORS. Backends are configured separately, below.

api:
enabled: true
port: 8018
ui:
enabled: true
sse:
enabled: true
cors:
origins:
- https://pwa.odio.love
- https://odio-pwa.vercel.app
KeyDefaultNotes
enabledtrueDisable to stop the server entirely.
port8018TCP port. Combined with bind to form the listen addresses.
ui.enabledtrueServes the embedded web UI at /ui. Auto-disabled at startup if bind does not include lo (the UI is loopback-only by design).
sse.enabledtrueServes the SSE event stream at /events. The embedded UI and the PWA both rely on it for real-time updates.
cors.originsPWA defaultsBrowser origins allowed to call the API, see below.

The default cors.origins covers the two hosted PWA addresses:

  • https://pwa.odio.love
  • https://odio-pwa.vercel.app

bind controls which network interfaces the API listens on.

bind: lo # loopback only (default)
# bind: enp2s0 # single LAN interface
# bind: [lo, enp2s0] # loopback + LAN
# bind: [lo, enp2s0, wlan0] # loopback + ethernet + wifi
# bind: all # all interfaces (Docker, remote access)

Since odio-api v0.12.0, ~/.config/odio-api/conf.d/ is loaded automatically: any *.yml file in that directory is merged on top of config.yaml at startup, so you can layer overrides on top of a base config without editing it. Within the systemd block, the system and user arrays are replaced wholesale by an override, not merged item by item, so the override must list every entry you want exposed.

On odio streamer installs, the installer creates this directory and drops a 10-systemd.yml.default reference matching the host's installed services, regenerated on every odio-upgrade run. The .default extension keeps it ignored at runtime. To use it as a starting point, copy it under a .yml name and edit:

Terminal window
cd ~/.config/odio-api/conf.d
cp 10-systemd.yml.default 50-my-systemd.yml
$EDITOR 50-my-systemd.yml
systemctl --user restart odio-api

Each backend can be enabled or disabled independently. Disabling a backend removes all its routes from the API.

See Systemd for endpoint details.

systemd:
enabled: true
timeout: 90s
system:
- name: bluetooth.service
user:
- name: mpd.service
url: ":8080"
- name: shairport-sync.service
- name: snapclient.service
url: "http://<snapserver>:1780"
- name: spotifyd.service
- name: upmpdcli.service

Since odio-api v0.12.0, each whitelist entry is a map with a name and an optional url. The URL is exposed on the /services response so clients (the embedded UI, PWA, Home Assistant) can render a direct link, which is how myMPD shows up alongside mpd.service on installs that ship it.

See Bluetooth for endpoint details.

bluetooth:
enabled: true
timeout: 5s
pairingTimeout: 60s
idleTimeout: 30m
scanTimeout: 60s

Since odio doesn't run as root, a few system configuration steps are required.

The user running odio must belong to the bluetooth group:

Terminal window
sudo usermod -a -G bluetooth <username>

PulseAudio or PipeWire must be configured to handle Bluetooth audio:

Terminal window
# PulseAudio
sudo apt install pulseaudio-module-bluetooth
# PipeWire
sudo apt install libspa-0.2-bluetooth

Edit /etc/bluetooth/main.conf to identify the node as an audio receiver:

[General]
Name = odio
Class = 0x240428
[Policy]
AutoEnable=false

Class of Device (0x240428) breakdown:

  • 0x24 — Major Device Class: Audio/Video
  • 0x0428 — Minor + services: Audio Sink, Loudspeaker, Rendering device

This makes the node appear as a standard Bluetooth speaker. Phones and computers will show a headphone icon and route media audio to it.

AutoEnable=false keeps the adapter off at boot — power is managed by the API (or Home Assistant).

After modifying the configuration, restart the Bluetooth service:

Terminal window
sudo systemctl restart bluetooth

BlueZ provides mpris-proxy, a systemd user service that creates an MPRIS player for each connected Bluetooth device. This is what allows the odio API to discover and control Bluetooth playback alongside all other sources.

Terminal window
systemctl --user status mpris-proxy.service

See Power for endpoint details.

power:
enabled: true
capabilities:
poweroff: true
reboot: true

On headless systems, logind requires a polkit rule to allow the odio user to reboot and power off without a graphical session:

/etc/polkit-1/rules.d/10-allow-shutdown.rules
polkit.addRule(function(action, subject) {
if ((action.id == "org.freedesktop.login1.power-off" ||
action.id == "org.freedesktop.login1.power-off-multiple-sessions" ||
action.id == "org.freedesktop.login1.reboot" ||
action.id == "org.freedesktop.login1.reboot-multiple-sessions") &&
subject.user == "odio") {
return polkit.Result.YES;
}
});

On desktop systems with a graphical session, this rule is not needed — logind already grants these permissions.

See Zeroconf for details. Requires bind to be set to a real network interface.

bind: enp2s0
zeroconf:
enabled: true