478 lines
20 KiB
Markdown
478 lines
20 KiB
Markdown
# 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:
|
|
|
|
```text
|
|
/
|
|
├── crabidy your saves: queues, bookmarks (`w`), and captures (`W`),
|
|
│ managed by the server
|
|
├── abs audiobookshelf audiobooks (absdy/README.md)
|
|
├── fs a local music folder (fsdy/README.md)
|
|
├── fyyd podcast search (fyyd/README.md)
|
|
├── jamendo Creative-Commons music (jamendody/README.md)
|
|
├── rss podcast subscriptions (rssdy/README.md)
|
|
├── soundcloud SoundCloud (soundclouddy/README.md)
|
|
├── tidal Tidal streaming (tidaldy/README.md)
|
|
├── youtube YouTube search & playlists (ytdy/README.md)
|
|
└── orphans store audio no save references any more — rename, delete,
|
|
or queue it
|
|
```
|
|
|
|
Full documentation — every provider, how to log in to each, every config
|
|
option, and the architecture — is the **book in [`docs/`](docs/src/)**:
|
|
|
|
```sh
|
|
devenv shell -- docs # serve it locally
|
|
```
|
|
|
|
## Binaries
|
|
|
|
- `crabidy-server` — the server: providers, queue, playback, gRPC on
|
|
`0.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 into `crabidy-server` (see
|
|
[cbd-web/README.md](cbd-web/README.md)).
|
|
|
|
## Quick start
|
|
|
|
The toolchain is managed by [devenv](https://devenv.sh):
|
|
|
|
```sh
|
|
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`.
|
|
|
|
### Tailored builds
|
|
|
|
Every provider — plus Opus decoding, the spectrum bars, the embedded web
|
|
UI, and the TUI's desktop notifications — sits behind a Cargo feature,
|
|
all on by default. Drop what you do not need and the dependencies go with
|
|
it (`opus-bundled` is the one to drop if you would rather link the system
|
|
libopus than compile the vendored copy):
|
|
|
|
```sh
|
|
# a local-files appliance: no network providers, no web UI, no FFT
|
|
cargo build --release -p crabidy-server --no-default-features --features fs,opus
|
|
```
|
|
|
|
`crabidy-server features` prints what a binary was built with. See
|
|
[docs/src/build-features.md](docs/src/build-features.md) for the full table,
|
|
what `fs` takes with it, and more examples.
|
|
|
|
**Note:** `--no-default-features` on its own drops **every** provider (it
|
|
compiles and runs, but plays nothing). Always name what you want.
|
|
|
|
## 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 | Configures | Login |
|
|
| --------------------- | ---------------- | ------------------ |
|
|
| `abs.toml` | audiobookshelf | URL + API key |
|
|
| `fsdy.toml` | local files | — |
|
|
| `fyyd.toml` | podcasts | none needed |
|
|
| `jamendo.toml` | Jamendo | none (key shipped) |
|
|
| `rss.toml` | podcast feeds | the feed URLs |
|
|
| `soundcloud.toml` | SoundCloud | optional token |
|
|
| `tidaly.toml` | Tidal | device login |
|
|
| `ytdy.toml` | YouTube | optional cookies |
|
|
| `cbd-tui.toml` | `cbd-tui` | server role + pw |
|
|
| `cbd.toml` | `cbd` | server role + pw |
|
|
| `crabidy-server.toml` | the server | its `[auth]` hashes|
|
|
|
|
Each provider's README — linked from the library tree at the top — explains
|
|
**how to log in** and documents every option in its file. The same material
|
|
is in the book under [`docs/src/providers/`](docs/src/providers/); the client
|
|
and server files are covered below.
|
|
|
|
Provider credentials and the client password are stored in **cleartext** —
|
|
these are config files, not a keyring. Keep `~/.config/crabidy/` private.
|
|
Crabidy redacts secrets from logs, errors, and config dumps.
|
|
|
|
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
|
|
[docs/src/store.md](docs/src/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`.
|
|
|
|
```toml
|
|
[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
|
|
|
|
# How those bars look. Defaults shown; a color is a "#rrggbb" triple, a
|
|
# color name, or a 0-255 palette index, and an unusable value warns and
|
|
# falls back. See docs/src/clients/tui.md for what each one does.
|
|
spectrum_color = "#bf616a" # the bars (the queue's playing-track red)
|
|
spectrum_gradient = true # shade them by height
|
|
spectrum_top_color = "#b48ead" # what the shading reaches at the top
|
|
spectrum_peak_color = "#81a1c1" # peak-hold shadows; "none" draws none
|
|
spectrum_peak_fill = true # false: a thin rule at the peak instead
|
|
spectrum_peak_fall = 10.0 # seconds for a full-scale shadow to fall
|
|
spectrum_bar_width = 1 # least bar width, in cells
|
|
spectrum_bar_gap = 1 # seam dividing two bars, in cells
|
|
spectrum_row_gap = 1 # line dividing two rows, in eighths
|
|
```
|
|
|
|
Every option except the `spectrum_*` appearance settings 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:
|
|
|
|
```sh
|
|
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:
|
|
|
|
```toml
|
|
providers = [
|
|
"tidal", "youtube", "fyyd", "abs", "soundcloud", "jamendo", "rss",
|
|
"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.
|
|
|
|
The list can only offer what the binary was **built** with (see [Tailored
|
|
builds](#tailored-builds)): the default list names only the providers this
|
|
build has, and naming one it lacks logs a warning at startup instead of
|
|
failing.
|
|
|
|
#### 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:
|
|
|
|
- **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.
|
|
|
|
```toml
|
|
[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:
|
|
|
|
```console
|
|
$ crabidy-server audio-devices
|
|
Audio output devices (* = selected by the current config):
|
|
hdmi:CARD=vc4hdmi,DEV=0
|
|
sysdefault:CARD=Headphones
|
|
...
|
|
```
|
|
|
|
Then pin one by passing it to the same command — the value is matched
|
|
case-insensitively as a substring of the name, so a memorable fragment is
|
|
enough — and restart the server:
|
|
|
|
```console
|
|
$ crabidy-server audio-devices Headphones
|
|
Set [audio] device = "Headphones" in .../crabidy-server.toml
|
|
```
|
|
|
|
That writes `[audio] device` for you; you can also edit it by hand:
|
|
|
|
```toml
|
|
[audio]
|
|
device = "Headphones"
|
|
```
|
|
|
|
If the name matches nothing, both the command and the server warn, and the
|
|
server 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):
|
|
|
|
```sh
|
|
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 into
|
|
`crabidy-server.toml`'s `[auth]`. Roles: `owner`, `queue-owner`,
|
|
`queue-appender`.
|
|
- `scan <path> [--capture|--move]` — walk a folder and drop a
|
|
`.cbd-track.toml` beside every audio file so it browses under `/fs`.
|
|
`--capture` copies each file into the content store (the toml points
|
|
there); `--move` moves it instead of copying.
|
|
- `audio-devices [device]` — list the audio output devices, or pin one into
|
|
`[audio] device` (see above).
|
|
- `features` — print the build features this binary has (see [Tailored
|
|
builds](#tailored-builds)).
|
|
|
|
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:
|
|
|
|
```sh
|
|
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
|
|
|
|
```sh
|
|
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.
|
|
|
|
`s` marks the selected row; `v` (or `V`) enters **visual mode**, where
|
|
movement marks or unmarks everything you sweep over, vim-style. The sweep
|
|
is anchored where you entered it, so moving back reverses it. `Esc` (or
|
|
any non-movement key) leaves visual mode. Both panes have marks and visual
|
|
mode.
|
|
|
|
In the queue this feeds a vim-style **register**: `y` yanks the selection
|
|
into it, `d` deletes the marked rows *into* it, and `c`/`C` fill it with
|
|
whatever they clear — so an accidental clear is recoverable. `p` pastes it
|
|
after the cursor and `P` before it, which makes `d` then `P` an exact undo
|
|
and `d` … `p` a move. The register is per client, in memory, one slot, and
|
|
holds paths — so a paste re-resolves (a yanked album expands to its
|
|
tracks). Note `p` no longer inserts the library selection: that flow is now
|
|
`y` on the left, then `p` on the right.
|
|
|
|
- `w` saves 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.
|
|
- `W` **captures** the selection into `/crabidy/<name>`: same as `w`, 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`, `d` deletes 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 `f`, or set the startup default with
|
|
`spectrum = false` in the client config. Servers built without the
|
|
`spectrum` feature simply never send bars.
|
|
|
|
The bars are shaded by height — red at the floor reaching purple at the top
|
|
— and divided into segments by a dim seam between bars and a thin line
|
|
between value rows. Each bar trails a blue peak-hold shadow marking where it
|
|
lately reached, falling away over a few seconds. Every part of that is
|
|
configurable, including turning it all off; see
|
|
[docs/src/clients/tui.md](docs/src/clients/tui.md).
|
|
|
|
`,` and `.` seek 15 seconds inside the playing track; `<` and `>` (or
|
|
`Ctrl-p`/`Ctrl-n`) skip a whole track. `K`/`J` change the volume and `m`
|
|
mutes; the now-playing pane shows the server's level, which tops out at
|
|
110%.
|
|
|
|
## 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`, marks and visual mode,
|
|
the `y`/`d`/`p` register, `,`/`.` to seek, queue and playback controls, `?`
|
|
for help), plus clickable equivalents for all of it: the progress bar seeks
|
|
where you click, and `library`/`queue` tabs in the top bar switch panes
|
|
without a keyboard. On a phone the two panes cannot sit side by side, so
|
|
only the focused one is shown and those tabs are the way between them.
|
|
There is 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). The `/` live filter is TUI-only for
|
|
now.
|
|
|
|
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:
|
|
|
|
```sh
|
|
devenv shell -- build-web # writes cbd-web/dist
|
|
cargo build -p crabidy-server # embeds it
|
|
```
|
|
|
|
For a headless, gRPC-only binary, build with every feature *except*
|
|
`web-ui` — e.g. `--no-default-features --features
|
|
all-providers,opus,spectrum`. See [cbd-web/README.md](cbd-web/README.md) for
|
|
the dev loop and details.
|
|
|
|
## Nix packages and cross-compiling
|
|
|
|
`flake.nix` (built with [crane](https://github.com/ipetkov/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:
|
|
|
|
```sh
|
|
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):
|
|
|
|
```sh
|
|
nix build .#crabidy-server-aarch64
|
|
scp ./result/bin/crabidy-server pi:/usr/local/bin/
|
|
```
|
|
|
|
The native package links the libopus that nixpkgs ships (`opus` without
|
|
`opus-bundled`, see
|
|
[docs/src/build-features.md](docs/src/build-features.md)), so nothing is
|
|
compiled from vendored C and the binaries carry a RUNPATH — they run from a
|
|
bare login shell, not only from this repo's dev shell.
|
|
|
|
Nix cross-compiles the Rust *and* the C dependencies (ALSA, libopus, 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
|
|
`*-Dockerfile`s) 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
|
|
|
|
```sh
|
|
devenv shell -- bash -lc 'cargo test --workspace --exclude cbd-web'
|
|
devenv shell -- check-features # the build-feature matrix
|
|
devenv shell -- docs # serve the documentation book
|
|
```
|
|
|
|
`AGENTS.md` (and `CLAUDE.md`) carry the coding rules and the toolchain
|
|
conventions. The reference documentation for the system itself lives in
|
|
[`docs/`](docs/src/).
|