15 KiB
crabidy
A client/server music player. A headless gRPC server owns the library, the play queue, and audio output; a terminal UI connects to it over localhost (or the network). Media comes from pluggable providers, each mounted as a subtree of one library:
/
├── tidal Tidal streaming (see tidaldy/README.md)
├── youtube YouTube search & playlists (see ytdy/README.md)
├── fyyd podcast search (see fyyd/README.md)
├── fs a local music folder (see fsdy/README.md)
├── crabidy your saves: queues, bookmarks (`w`), and captures (`W`),
│ managed by the server (see architecture/crabidy-store.md)
└── orphans store audio no save references any more — rename, delete,
or queue it (see architecture/orphans.md)
Binaries
crabidy-server— the server: providers, queue, playback, gRPC on0.0.0.0:50051. Also serves the web client at that address (see below).cbd-tui— the terminal client. Press?inside for all key bindings.cbd— both in one process: starts the server, waits until it accepts connections, then runs the TUI. Adopts an already-running server instead of failing on an occupied port.cbd-web— the browser client (Leptos/WASM), with the same functionality as the TUI. Not run directly: it is built to a bundle and embedded intocrabidy-server(see cbd-web/README.md).
Quick start
The toolchain is managed by devenv:
devenv shell # provides rust, yt-dlp, and friends
cargo run -p cbd # server + TUI in one process
Or run the halves separately: cargo run -p crabidy-server and, in
another terminal, cargo run -p cbd-tui.
Configuration
All configuration lives in ~/.config/crabidy/ (the platform config
directory). Every file is optional; missing providers simply do not
mount. Files are created/rewritten on first start with their defaults
filled in.
| File | Component | Documentation |
|---|---|---|
tidaly.toml |
Tidal | tidaldy/README.md |
ytdy.toml |
YouTube | ytdy/README.md |
fyyd.toml |
podcasts | fyyd/README.md |
fsdy.toml |
local fs | fsdy/README.md |
cbd-tui.toml |
cbd-tui |
below |
cbd.toml |
cbd |
below (same options as cbd-tui.toml) |
crabidy-server.toml |
server | below (providers + auth) |
The server-managed crabidy provider does not live under ~/.config. Its
track-file tree (saved queues, bookmarks, and captures) lives in
~/.local/state/crabidy/ (the platform state directory) and the audio it
captures lives in a single content-addressed store under
~/.local/share/crabidy/ (the data directory), shared and de-duplicated
across saves. Neither needs configuration; see
architecture/crabidy-store.md.
cbd-tui.toml and cbd.toml
Client configuration. cbd-tui (the standalone terminal client) reads
cbd-tui.toml; cbd (server + TUI in one process) reads its own
cbd.toml. They are separate files with the same options so the two
can run side by side on one machine — a common setup is cbd playing
locally against its in-process server while cbd-tui points at a remote
server (e.g. a Raspberry Pi). A shared file would force one to follow
the other's address.
[server]
# Where to find the server. Default (both files): localhost, which is
# what cbd's own in-process server listens on. Point cbd-tui.toml at a
# remote server to use it as a remote control.
address = "http://127.0.0.1:50051"
# Credentials, when the server has [auth] configured (see below).
# `user` is the role name; leave both empty against an open server.
# The password is stored in plaintext — keep this file private.
user = ""
password = ""
# Show the frequency-spectrum bars under the track progress. Default true.
spectrum = true
Every option is also available as a command-line flag before the
subcommand (cbd-tui --address ... --user owner, cbd --spectrum false); a flag overrides the file value. To write the credentials into
the config once, use the auth subcommand (see below) instead of
editing the file by hand:
cbd-tui auth owner 'my-password' # sets user + password
cbd-tui auth queue-owner 'pw' --address http://pi:50051
crabidy-server.toml — providers and rights
On first start the server writes this file with every provider enabled:
providers = ["tidal", "youtube", "fyyd", "fs", "crabidy", "orphans"]
Remove a name to disable that provider — it no longer mounts and does
not appear in the library. Deleting the whole providers line re-enables
everything (a fresh install with no file behaves the same). Disabling
crabidy also drops orphans, which is a view over the store. A disabled
provider's own config file (tidaly.toml, etc.) is simply left unread.
Roles and rights
By default the server is open: everyone who can reach the port has
full control. Setting password hashes in [auth] locks the server
from the top down — each password you set lowers what a
no-credential caller may do, while a matching password elevates a
caller to that role (see architecture/roles-auth.md):
- owner — everything (the normal user).
- queue-owner — anything on the queue and playback, but no
library writes: no bookmarks (
w), captures (W), queue saving, renames or deletes. - queue-appender — may browse/search and append tracks to the queue; nothing else.
A caller with no credentials gets the highest role you left unguarded:
nothing guarded → owner (the open default); guard owner → anonymous is
queue-owner; guard owner + queue_owner → anonymous is queue-appender;
guard all three → credentials required for everything.
[auth]
# One PHC hash per role. Guard from the top down. Generate and store a
# hash with: crabidy-server guard <role> (see the CLI section).
owner = "$argon2id$v=19$m=19456,t=2,p=1$..."
queue_owner = "$argon2id$..."
queue_appender = "$argon2id$..."
Clients authenticate with the role name as the basic-auth user (see
cbd-tui.toml above). A malformed crabidy-server.toml — or one that
guards a lower role while a higher one is still open — aborts server
startup rather than silently running open. Note that the transport is
plain HTTP/2: fine on a trusted home network, but anything exposed
further needs TLS termination (reverse proxy, VPN) in front.
Audio output device
By default the server plays to the system default output device. On a Raspberry Pi that is often HDMI, so playback runs but you hear nothing on the headphone jack or a USB/DAC. List the devices the server can see:
$ crabidy-server audio-devices
Audio output devices (* = selected by the current config):
hdmi:CARD=vc4hdmi,DEV=0
sysdefault:CARD=Headphones
...
Then pin one in crabidy-server.toml — the value is matched
case-insensitively as a substring of the name, so a memorable fragment is
enough — and restart the server:
[audio]
device = "Headphones"
If the name matches nothing, the server logs a warning and falls back to the system default.
Command line
Every binary is a clap CLI: run it with --help (and any subcommand
with --help) for the full surface. Running a binary with no
subcommand behaves as it always has — crabidy-server runs the
server, cbd-tui runs the TUI, cbd runs the in-process server + TUI.
The library, queue, and global subcommands are available on all
three binaries and act as a remote control over gRPC (they connect to a
running server, honouring the same [auth] credentials as the TUI):
crabidy-server library list /tidal # browse a node
cbd-tui --address http://pi:50051 queue append /fs/album
cbd global play # toggle play/pause
cbd global volume -- -0.1 # lower the volume
Connection flags (--address/--user/--password) go before the
subcommand; omitted, they fall back to the client config file.
Server-only subcommands (crabidy-server, and cbd):
guard <role> [password]— hash a role password (argon2id), print the PHC string, and (unless--no-config) write it intocrabidy-server.toml's[auth]. Roles:owner,queue-owner,queue-appender.scan <path> [--capture|--move]— walk a folder and drop a.cbd-track.tomlbeside every audio file so it browses under/fs.--capturecopies each file into the content store (the toml points there);--movemoves it instead of copying.
Client-only subcommand (cbd-tui, and cbd):
auth <role> [password] [--address ADDR]— write the role name and cleartext password (and address) into the client config.
Password caveat. A password given as a command-line argument is
visible in the process list (e.g. ps). Omit it and guard reads the
password from stdin instead, which keeps it out of argv and is
pipe-friendly:
printf '%s' 'my-password' | crabidy-server guard owner
The client config stores the password in plaintext, so keep the file private.
Completions and man pages
cbd-tui completions bash # print a completion script
devenv shell -- gen-cli-assets # write dist/completions + dist/man
gen-cli-assets builds the binaries with CBD_ASSET_DIR=$PWD/dist, so
dist/completions/** (bash/zsh/fish) and dist/man/*.1 are produced
for all three binaries. Every ordinary build also emits them into the
crate's OUT_DIR.
Using the library
Navigation is vim-style: j/k select, l enters the selected
folder, h goes to the parent, Tab switches between library and
queue, Enter replaces the queue with the selection. % creates a
node where the pane title shows % to add (e.g. a search term), e
renames, d deletes. / filters the current pane (library or queue)
live as you type — Enter keeps the filter, Esc clears it.
wsaves the selection (a library subtree, or in the queue pane the queue) as a new folder under/crabidy/<name>of link files — needs the source provider to replay. On a name that already exists the save is refused with a warning; delete the old folder and save again.Wcaptures the selection into/crabidy/<name>: same asw, but every track's audio is fetched into the shared content store under~/.local/share/crabidy/and the saved tomls link to it — fully local playback afterwards. Works on a library subtree and on the queue (no need to save it first). Audio is de-duplicated: capturing the same track again (from a playlist, a search, another save) reuses the stored file instead of downloading it twice, matched first by provider id and then by content hash. Tracks already local (from/fs) are copied into the store rather than re-downloaded. A source that genuinely cannot be captured is recorded as skipped (red in the UI, skipped by playback). Download captures can take long; progress is shown in the library pane.- Captured rows are marked with a trailing
↓(down-arrow) at the end of the row — visible even while browsing another provider, so you can see what you already have. - Inside
/crabidy,ddeletes a folder or track immediately (no confirmation): it removes only the metadata toml, never the shared store audio, which other saves may reference.
Press ? for the full binding table.
A row of frequency-spectrum bars is drawn under the track progress
while audio plays (the server taps its own output, runs the FFT, and
streams the bars, so it works whether the server is local or remote).
Toggle it at runtime with v, or set the startup default with
spectrum = false in the client config.
Web client
crabidy-server serves a browser client with the same functionality as
the TUI at its own address (http://<server>:50051/) — same navigation,
same keys (j/k/h/l, %, e, d, w, W, queue and playback
controls, ? for help), plus clickable equivalents and a light/dark
theme toggle. It talks gRPC-web to the same service the TUI uses, so it
honors the same [auth] roles (it shows a login form when the server
requires credentials).
It is compiled to a WASM bundle and embedded into the server binary,
behind the default-on web-ui cargo feature. A plain cargo build
needs no WASM toolchain — it embeds a "not built" placeholder page until
you build the bundle:
devenv shell -- build-web # writes cbd-web/dist
cargo build -p crabidy-server # embeds it
Build the server with --no-default-features for a headless,
gRPC-only binary. See cbd-web/README.md for the
dev loop and details.
Nix packages and cross-compiling
flake.nix (built with crane) packages
the binaries as Nix derivations — for your own machines, and cross-compiled
for a Raspberry Pi. No Docker required.
Native — install on any machine with Nix:
nix run .#cbd-tui # run without installing
nix build .#crabidy # cbd, cbd-tui, crabidy-server → ./result/bin
nix profile install .#crabidy # or github:OWNER/crabidy once pushed
Raspberry Pi (or any aarch64 Linux) — a fully static musl binary, so it has no glibc-version or loader dependency and runs on stock Raspberry Pi OS (bookworm and newer):
nix build .#crabidy-server-aarch64
scp ./result/bin/crabidy-server pi:/usr/local/bin/
Nix cross-compiles the Rust and the C dependencies (ALSA, aws-lc)
hermetically on an x86_64 host — the isolated build avoids the host-linker
pitfalls of cross-compiling in a plain shell. The aarch64 server includes
the embedded web UI: the flake builds the cbd-web wasm bundle with trunk
(pinning a wasm-bindgen CLI that matches the crate) and stages it into the
server's web-ui feature. The native .#crabidy package stays headless; use
a normal cargo build there if you want the bundle.
A container-based cross setup also exists (Cross.toml + the
*-Dockerfiles) for building against Debian's glibc, but the flake is the
recommended path.
Logs
cbd and cbd-tui log to ~/.local/state/crabidy/ (daily files);
crabidy-server logs to stderr. Stream URLs and credentials are
redacted from logs by design.
Development
Design documents live in architecture/, per-feature quality gates in
quality/, and implementation plans in plan/. See CLAUDE.md /
AGENTS.md for the development workflow and coding rules.