Ichoi is a music player, library manager, and distributed jukebox server. One static binary runs your library, streams to browsers and native clients, and turns speakers on other machines — a Raspberry Pi in the kitchen, a tablet by the door — into shared, controllable playback targets that anyone you permit can queue music to.
It is deliberately not a Subsonic clone. Ichoi has no passwords (identity is
LinkKeys, or login-less on a trusted
LAN), a real capability-negotiated media pipeline, and a CSIL-native API — the
web UI, native apps, and satellites all speak the same transport. See
docs/DESIGN.md for the full architecture and the reasoning behind
every decision, and CONTRIBUTING.md for working conventions.
Status: design. The architecture is settled; implementation is in progress.
- Streams full-quality or transcodes on demand. The client always runs the same Symphonia decoder; the core sends original packets (direct mode) or re-encodes to AAC-LC / MP3 (transcoded mode) based on what each consumer negotiates — bitrate cap, codec, or an undecodable source (Opus, HE-AAC). Transcoding shells out to a bundled static ffmpeg; it is never linked.
- Distributed jukebox. Satellites connect out to a core and register their outputs as shared targets. Control and audio travel back down the connection the satellite opened, so NAT'd Pis just work. The core never connects to a satellite.
- Multi-server clients. One client can browse several servers at once and, when permitted, copy albums and playlists between them (never delete).
- Browser-first, served by the same binary over a WebSocket carrier, with a Symphonia→WASM decoder. Firefox is first-class. Themeable, i18n + a11y from day one.
- Gapless, sample-accurate playback with time-based seeking.
All three are the same binary; the role is configuration.
| Role | What it is |
|---|---|
| core | Owns the library, database, ffmpeg, and TLS keys. Serves the API, indexes and transcodes, coordinates jukebox state, accepts client-mediated imports. May own local outputs. |
| satellite | A dumb output node (typically a Pi). Connects out to one core, registers its speakers as shared targets, plays AAC-LC. No library access. |
| client | A browser or native app carrying a LinkKeys identity. Browses one or more servers, plays locally, copies between servers, and may become a shared satellite of one server. |
# Run a core over your music library.
ICHOI_MUSIC_DIR=/path/to/music ichoi serveThat serves the browser UI on http://localhost:4042 and the CSIL+TLS listener on
:4043. On a trusted LAN this is login-less (guest); the first LinkKeys login promoted
with ICHOI_ADMIN_TOKEN becomes the admin.
Browsers should reach Ichoi over HTTPS via a reverse proxy — the HTTP listener is plain
HTTP by design. See deploy/ for a ready-to-edit Caddy example and a
docker-compose that runs Ichoi behind it.
Precedence is environment (ICHOI_-prefixed) → TOML config → defaults. The common
settings:
| Setting | Env | Default |
|---|---|---|
| Role | ICHOI_ROLE |
core |
| Music directory | ICHOI_MUSIC_DIR |
(required for a core) |
| Audiobook directory | ICHOI_AUDIOBOOK_DIR |
unset |
| Flatten disc subfolders into parent albums | ICHOI_ALBUM_SUBFOLDER_FLAT |
true |
| Disc-subfolder matching words | ICHOI_ALBUM_SUBFOLDER_WORDS |
cd,disc,disk,bonus disc |
| Database directory | ICHOI_DB_DIR |
music dir |
| HTTP listen | ICHOI_HTTP_ADDR |
:4042 |
| CSIL listen | ICHOI_CSIL_ADDR |
:4043 |
| Core address (satellite) | ICHOI_CORE_ADDR |
(required for a satellite) |
| Core key fingerprints (satellite) | ICHOI_CORE_KEYS |
(required for a satellite) |
| Node token (satellite) | ICHOI_NODE_TOKEN |
(required for a satellite) |
| CSIL/TLS certificate (core) | ICHOI_TLS_CERT |
<db-dir>/csil-cert.der |
| CSIL/TLS private key (core) | ICHOI_TLS_KEY |
<db-dir>/csil-key.der |
| Admin bootstrap token | ICHOI_ADMIN_TOKEN |
unset |
| DNS-less LinkKeys RP | ICHOI_LINKKEYS_LOCAL_RP |
disabled; only exact true enables |
| Local RP name | ICHOI_LINKKEYS_LOCAL_RP_NAME |
unset |
| Regular LinkKeys RP | ICHOI_LINKKEYS_RP |
disabled; only exact true enables |
| RP TCP endpoint | ICHOI_LINKKEYS_RP_ADDR |
unset |
| RP TLS fingerprints | ICHOI_LINKKEYS_RP_FINGERPRINTS |
unset |
| RP API key | ICHOI_LINKKEYS_RP_API_KEY or ICHOI_LINKKEYS_RP_API_KEY_FILE |
unset |
| RP domain | ICHOI_LINKKEYS_RP_DOMAIN |
unset |
| Public HTTPS origin | ICHOI_PUBLIC_URL |
unset |
| Trusted LinkKeys identities | ICHOI_LINKKEYS_TRUSTED_IDENTITIES |
unset |
| ffmpeg override | ICHOI_FFMPEG |
bundled → PATH |
| Default transcode codec | ICHOI_TRANSCODE_CODEC |
aac |
| Log level | ICHOI_LOG |
warn |
Setting ICHOI_AUDIOBOOK_DIR enables the Audiobooks view. The directory may be separate
from the music collection or nested below it; a nested audiobook tree is excluded from the
music catalog automatically. Signed-in users get independent per-chapter listening progress
and resume positions. While the server has no accounts, guest playback uses one global progress
profile shared by everyone browsing that server.
By default, disc-like subfolders such as CD1, CD2, Disc One, and Bonus Disc are
fuzzy-matched and flattened into one album named after their parent folder. Track titles retain
their source folder as a prefix, for example CD1 - Chapter 01. Set
ICHOI_ALBUM_SUBFOLDER_FLAT=false to retain separate subfolder albums, or provide a comma-separated
ICHOI_ALBUM_SUBFOLDER_WORDS list to replace the default matching vocabulary.
The database is a single SQLite file (WAL) in the database directory. ffmpeg is resolved
bundled-next-to-the-binary first, then PATH; if neither is found, transcoding is
disabled and the core serves direct mode only — Ichoi never fails to start for lack of
ffmpeg.
Administrators can start a full library resync from Settings. Resync uses the same single-flight scanner as startup, updates changed metadata, and removes catalog rows for files that disappeared; starting another resync while one is active does not create a competing scan.
The default web UI is an installable PWA. An administrator (or anyone on a login-less guest
instance) can create a named, one-time satellite token in Settings and enter it at /satellite.
The token is stored only in that browser profile. While present, every other application route
redirects to the restricted satellite console; leaving satellite mode and signing in normally are
separate, explicit actions.
Satellite tokens define whether newly discovered audio outputs are shared and which access groups
inherit them. The built-in Everyone group includes guests and every account. Administrators can
create narrower groups, change an individual output's groups, or disable an output globally so it
is not listed or controllable by anyone. The console supports Chrome's audio-output chooser when
available, falls back to the system default, and offers a fullscreen action. ChromeOS still requires
a user gesture through Enable audio before remote playback and a personal ChromeOS login after a
reboot; session restore can reopen the installed PWA afterward.
Local-RP login is completely opt-in and gives Ichoi a key-fingerprint identity rather than a public DNS identity or RP sidecar. Enable it with all three settings:
ICHOI_LINKKEYS_LOCAL_RP=true
ICHOI_LINKKEYS_LOCAL_RP_NAME="Kitchen Ichoi"
ICHOI_LINKKEYS_TRUSTED_IDENTITIES="family.example,alice@friends.example"Only the exact lowercase value true enables the mode. A domain entry accepts every
verified handle at that LinkKeys domain; handle@domain accepts only that verified handle.
Users normally enter handle@domain in Ichoi's sign-in dialog. Ichoi derives its callback
from the browser's same-origin LAN address, so no public URL setting or public availability
is required.
The first enabled start generates the local RP signing/encryption identity and stores the
SDK's opaque key bundle in ichoi.db. It remains stable across restarts; changing the name
does not silently rotate it. Backups of the database therefore contain private RP key
material and must be protected. Approve the fingerprint reported in the log or /api/auth
at each trusted LinkKeys domain before users log in.
For a public deployment backed by an existing DNS-pinned LinkKeys RP service, enable regular-RP mode instead. It is mutually exclusive with DNS-less mode:
ICHOI_LINKKEYS_RP=true
ICHOI_LINKKEYS_RP_ADDR="linkkeys.example.com:4987"
ICHOI_LINKKEYS_RP_FINGERPRINTS="fingerprint1,fingerprint2"
ICHOI_LINKKEYS_RP_API_KEY_FILE="/run/secrets/linkkeys_rp_api_key"
ICHOI_LINKKEYS_RP_DOMAIN="ichoi.example.com"
ICHOI_PUBLIC_URL="https://ichoi.example.com"
ICHOI_LINKKEYS_TRUSTED_IDENTITIES="family.example,alice@friends.example"The API key must belong to an RP service account with LinkKeys' api_access
relation. The file form is preferred for containers because it keeps the key
out of Compose interpolation and the container environment. Ichoi pins the RP
server's TLS certificate to the configured fingerprints, requests and verifies
the handle claim, and applies the same domain/handle admission selectors used
by DNS-less mode.
Satellites authenticate with a node token only after establishing rustls TLS to a pinned core key. No DNS name or public CA is involved. On the core, print the fingerprint that must be provisioned out of band:
ichoi core-fingerprintThen install the same binary on the output machine. Reading the token from a file or stdin keeps it out of shell history and process listings:
ichoi install satellite \
--core 192.168.1.20:4043 \
--core-key sha256:<fingerprint> \
--node-token-file ./theater-node.tokenUse --dry-run to print every file and service-manager command without changing the host.
--scope user selects a per-user installation; the defaults are a system service on Linux
and a user service on macOS and Windows.
Inspect or remove the same installation with:
ichoi status --scope user
ichoi uninstall --scope user --keep-config- Linux installs a systemd system or user unit. System scope uses
/etc/ichoi/ichoi.toml; user scope follows$XDG_CONFIG_HOME. - macOS installs a LaunchAgent under
~/Library/LaunchAgentsand keeps its configuration under~/Library/Application Support/Ichoi. - Windows user scope installs an
ONLOGONscheduled task. System scope installs a native Windows Service and stores configuration under%PROGRAMDATA%\Ichoi.
Linux retains its runtime-loaded ALSA backend. macOS and Windows builds compile only their native CoreAudio/WASAPI backend, so those dependencies never enter Linux artifacts.
Each independently versioned target lives in its own top-level directory (the
multiple-directory release strategy — a future mobile app would be a sibling mobile/).
The server target is under server/; the repo root holds only shared CI
(.reactorcide/), project docs, and the ./tools.sh helper.
Ichoi is a single static binary with zero system-library link dependencies. SQLite is
compiled in; ffmpeg and host audio are never linked. Direct cargo runs from the server
target dir; use the musl targets for a fully static build:
cd server
cargo build --release --target x86_64-unknown-linux-musl # amd64
cargo build --release --target aarch64-unknown-linux-musl # arm64 (Pi satellites)Day-to-day, use ./tools.sh from the repo root (it drives the server target
for you), which mirrors what CI runs:
./tools.sh build # cargo build --workspace
./tools.sh test # TEST_DATABASE_BACKEND=sqlite cargo test --workspace
./tools.sh fmt # cargo fmt --all
./tools.sh clippy # cargo clippy --workspace --all-targets -- -D warnings
./tools.sh check # csil-validate + fmt + clippy + test (the full local gate)
./tools.sh gen # regenerate CSIL bindings (server + all client languages)The server/Dockerfile produces a minimal scratch image (multi-arch,
linux/amd64 + linux/arm64) containing the ichoi binary, a bundled static LGPL
ffmpeg, and the web UI. Scratch is safe because host audio is loaded at runtime — a
server needs no audio output, so the image just reports audio_outputs: none.
docker buildx build --platform linux/amd64,linux/arm64 -t ichoi:dev server
docker run --rm -e ICHOI_MUSIC_DIR=/music -v /path/to/music:/music -p 4043:4043 ichoi:devPublished images live at containers.catalystsquad.com/public/catalystcommunity/ichoi.
For a full deployment behind TLS, see server/deploy/docker-compose.yml.
Tests hit a real SQLite database inside a transaction that rolls back — no mocks for
the data layer, every test isolated in its own transaction. CI runs on
Reactorcide (not GitHub Actions):
conventional commits gate PRs and fan out to build (amd64 + arm64), the SQLite suite, and
CSIL validation; semver-tags cuts per-target releases (server/vX.Y.Z) on merge to
main. ./tools.sh check reproduces the gate locally. Definitions live in
.reactorcide/jobs/.
Apache-2.0. See LICENSE. Nothing copyleft or patent-encumbered lives inside
the Ichoi binary; the bundled ffmpeg is a separate LGPL program, subprocessed (see
docs/ffmpeg.md).