The root manifest is a pure [workspace], so crane finds no [package].name
to name its derivations after and falls back to a placeholder — every
build of crabidy logged two "crane will use a placeholder value" warnings
and produced cargo-package-0.1.0.drv, which says nothing about what was
built.
Name it in [workspace.metadata.crane], the spelling crane itself
suggests. Cargo ignores unknown workspace metadata, so this is a build
label and nothing more.
The cross build takes its name from the same root, which would leave the
native and aarch64 derivations both called crabidy-0.1.0; give it an
explicit pname so a build log can tell them apart.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The recollection that this used to exist is false — `git log --all -S
mpris` finds nothing on any branch. What has always been there is the
desktop *notification* on a track change, which is D-Bus but not MPRIS: a
popup is neither a status-bar entry nor a key target. So this is new, not
a regression.
Everything the protocol needs was already on the wire, so cbd-tui gains a
second front-end onto the two channels it already has: stream updates in,
MessageFromUi out. The MPRIS player is a peer of the UI thread — it
commands the server through the very channel the keybindings use, and it
learns the result the way the UI learns about a keypress from another
client. No proto change, no server change.
The decisions worth knowing (architecture/mpris.md):
- An absolute protocol over a toggling server. Play/Pause/SetShuffle/
SetLoopStatus consult the last state the server broadcast and send
nothing when it already matches, or the pause key would start playback
on a paused player. Volume is the same idea with arithmetic; muting is
spelled "volume 0", and the setter mutes on a zero target so the level
survives to be unmuted to.
- No URL reaches the bus. xesam:url would have to be the stream URL,
which clients never see and which several providers sign with
credentials, and every peer on a session bus can read properties. The
trackid is the queue position — also the only spelling that is a valid
object path.
- mpris:length is omitted when unknown rather than sent as zero, which
would make consumers draw a full progress bar.
- Unrepresentable requests are refused, not approximated: repeat-one,
rates other than 1.0, OpenUri, Raise, and Quit — a status-bar button
has no business closing someone's terminal.
- No session bus is a normal way to run (ssh, a tty, a container): the
connection carries a timeout and its failure is an info log, after
which the client behaves exactly as before.
Behind the `mpris` feature, on by default beside `notifications` and
forwarded by `cbd`; the nix package names it in headlessFeatures, since
naming a feature set at all replaces the crate defaults. It costs one
crate and no system library — zbus speaks D-Bus in pure Rust and
notify-rust had already brought it in.
Verified with the real thing, not only a test double: under
dbus-run-session, playerctl lists the player, reads its metadata
("Playing: the artist - the song (4:00)"), and drives play-pause,
`position 30+` and `volume 0.8` into the right commands. It also refuses
`next` when the queue is empty, which is CanGoNext being honest. The
committed bus test covers the round trip and skips where there is no bus.
`opus` conflated two axes: decoding Ogg-Opus, and vendoring the C library
to do it. So the only way to avoid a cmake build of libopus was to give up
Opus playback -- the wrong trade for Nix, which already ships one.
The adapter crate draws the line already: symphonia-adapter-libopus own
`bundled` feature is what pulls opusic-sys/bundled and with it cmake. So
declare the dependency `default-features = false` and add
`opus-bundled = ["symphonia-adapter-libopus?/bundled"]`, forwarded up
through crabidy-server and cbd. It stays in every `default`, so a plain
cargo build still needs nothing installed; opting out is the packager act.
The weak `?/` is load-bearing -- a plain `/bundled` would enable the
optional dependency itself, and `opus-bundled` would quietly become a
second "do we decode Opus" flag.
The flake native build then drops cmake and takes libopus from nixpkgs;
`headlessFeatures` already omitted opus-bundled, so it opts out for free.
The cross build keeps the vendored copy: it links statically, and an
unbundled -lopus would need a static aarch64 libopus staged for the target
the way alsa-lib is.
That exposed an older bug. rustc stamps no RUNPATH, and the -L from
buildInputs arrives through NIX_LDFLAGS, which ld-wrapper does not mirror
into the binary -- so the package linked cleanly and then refused to start.
RUNPATH was empty, meaning libasound.so.2 had never resolved either: the
package always depended on the caller having it on LD_LIBRARY_PATH, which
this repo dev shell happens to set. autoPatchelfHook now fills the RUNPATH
from buildInputs (plus stdenv.cc.cc for libgcc_s, the compiler own
unwinding runtime, which no crate declares) and fails the build on anything
it cannot resolve. Verified by running each binary under `env -i`, and with
LD_BIND_NOW=1 so every opus symbol binds eagerly.
Also: devenv sets OPUS_LIB_DIR, without which the unbundled build dies as
"mold: fatal: library not found: opus"; check-features *builds* the two
libopus variants rather than clippy-ing them, since clippy links nothing and
cannot tell a resolvable -lopus from a missing one; and cbd was missing an
`rss` pass-through, so the bundle could not select that provider alone.
Requested alongside this: the spectrum shadows fall over 10 seconds instead
of 4. The config default and SpectrumStyle::default are two spellings of
one thing, so the config test now asserts the whole resolved style equals
SpectrumStyle::default() rather than field-by-field.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
nix build .#crabidy-server-aarch64 and .#crabidy have both been broken
since Opus decoding landed: symphonia-adapter-libopus pulls opusic-sys,
which compiles a bundled libopus with CMake. devenv.nix got cmake at the
time, flake.nix never did, so the derivation died with "is `cmake` not
installed?" while building the deps.
Both flake derivations now carry pkgs.cmake, and so do the three cross
Dockerfiles, which had the identical gap. No ninja anywhere: its mere
presence flips cmake's generator and then clashes with a build dir cached
under the other one.
Verified: nix build .#crabidy-server-aarch64 completes, and its output is
a 39 MB statically linked aarch64 ELF with every provider, the embedded
wasm bundle, and the Opus decoder.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
flake.nix first: its native package passed a bare --no-default-features,
which used to mean "everything but web-ui" and now means *no providers
at all*. It names its set explicitly
(all-providers,opus,spectrum,notifications); the aarch64 cross build
keeps the full defaults and its staged wasm bundle.
docs/src/build-features.md: the feature table with what each one costs to
lose, why fs takes /crabidy, /orphans, queue persistence and scan with
it, the opus/libopus build note, two worked examples, what is
deliberately not gated, and check-features. Linked from SUMMARY.md, and
config.md now says the providers list can only offer what the binary was
built with. README gains a short pointer.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The static aarch64 crabidy-server now ships the browser UI. crane builds
the cbd-web wasm bundle with trunk, then stages it into the server's
`web-ui` feature (build.rs embeds cbd-web/dist).
- webSrc: a source variant that keeps cbd-web's non-cargo assets
(index.html, style.css, Trunk.toml) that filterCargoSources drops,
minus any stale prebuilt dist/.
- wasmBindgenCli: nixpkgs ships an older wasm-bindgen CLI and the dev
shell lets trunk download the matching one at build time, which a
sealed Nix build can't do — so pin an overridden CLI at 0.2.126 to
match the wasm-bindgen crate.
- webBundle: buildTrunkPackage over a wasm32 toolchain; build from
inside cbd-web (virtual workspace ⇒ trunk can't resolve the member
from the root) and index.html-first so trunk's optional-valued
--release doesn't swallow the positional.
- The aarch64 server drops --no-default-features (web-ui back on) and a
preBuild stages the bundle into cbd-web/dist before the crate compiles.
Verified: nix build .#crabidy-server-aarch64 produces a static aarch64
ELF whose embedded index.html carries the real hashed wasm/js assets
(not the headless placeholder). Native .#crabidy stays headless.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`flake.nix` packages the binaries with crane, no Docker:
- `nix build .#crabidy` / `nix profile install .#crabidy` — native cbd,
cbd-tui, crabidy-server for any machine with Nix; `nix run .#cbd-tui`.
- `nix build .#crabidy-server-aarch64` — a fully static aarch64 musl server
(no glibc/loader dependency), so it runs on stock Raspberry Pi OS. Nix
cross-compiles the Rust and the C deps (ALSA, aws-lc) hermetically on an
x86_64 host; the isolated derivation avoids the host-linker contamination
that plagues cross-linking in a plain devenv shell.
The source filter keeps crabidy-core's *.proto (crane would otherwise drop
it); builds are limited to the real bin crates with -p so the wasm cbd-web
crate is never built for a host/aarch64 target. Packages are headless
(--no-default-features) — the embedded web UI stays a normal cargo build.
The legacy container `cross` path (Cross.toml + *-Dockerfile) is left as-is;
the flake is the recommended cross path. README documents both.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>