317 lines
13 KiB
Markdown
317 lines
13 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
|
|
/
|
|
├── tidal Tidal streaming (see tidaldy/README.md)
|
|
├── youtube YouTube search & playlists (see ytdy/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 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`.
|
|
|
|
## 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](tidaldy/README.md) |
|
|
| `ytdy.toml` | YouTube | [ytdy/README.md](ytdy/README.md) |
|
|
| `fsdy.toml` | local fs | [fsdy/README.md](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](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`.
|
|
|
|
```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
|
|
```
|
|
|
|
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:
|
|
|
|
```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", "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. Adding an `[auth]` section turns on HTTP basic auth for
|
|
every RPC and hands out *roles* (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.
|
|
|
|
```toml
|
|
[auth]
|
|
# One PHC hash per role; omit a role to disable it. 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` 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.
|
|
|
|
## 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.
|
|
|
|
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.
|
|
|
|
- `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 `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:
|
|
|
|
```sh
|
|
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](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/
|
|
```
|
|
|
|
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 flake packages are headless
|
|
(no embedded web UI); use a normal `cargo build` if you need 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
|
|
|
|
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.
|