A modern, network-enabled music player platform built on Rockbox technology.
rockboxd.tsiry-sandratraina.com
rust
deno
navidrome
airplay
libadwaita
zig
mpris
snapcast
mpd
rockbox
audio
subsonic
29 kB
641 lines
1{
2 description = "Rockbox Daemon — rockboxd daemon (gRPC/GraphQL/HTTP/MPD audio server)";
3
4 inputs = {
5 # The Rust workspace depends on path crates under the `deno` git
6 # submodule (deno/cli, deno/runtime, deno/ext/*, …). Pull the flake's
7 # own submodules into `self` so `src = ./.` carries them; without this
8 # `cargo build` fails with "failed to read deno/cli/Cargo.toml".
9 self.submodules = true;
10
11 nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05";
12 flake-utils.url = "github:numtide/flake-utils";
13 rust-overlay = {
14 url = "github:oxalica/rust-overlay";
15 inputs.nixpkgs.follows = "nixpkgs";
16 };
17 };
18
19 outputs = { self, nixpkgs, flake-utils, rust-overlay }:
20 # x86_64-darwin is intentionally omitted: nixpkgs 26.05 is the last
21 # release to support it, and some dev-shell deps (e.g. babashka) already
22 # drop it from meta.platforms, which breaks whole-flake evaluation
23 # (e.g. FlakeHub's cross-system `nix eval`).
24 flake-utils.lib.eachSystem [
25 "x86_64-linux"
26 "aarch64-linux"
27 "aarch64-darwin"
28 ] (system:
29 let
30 overlays = [ (import rust-overlay) ];
31 pkgs = import nixpkgs { inherit system overlays; };
32 lib = pkgs.lib;
33
34 # ── Rust 1.95 stable ────────────────────────────────────────────────
35 rustToolchain = pkgs.rust-bin.stable."1.95.0".default.override {
36 extensions = [ "rust-src" "rustfmt" "clippy" ];
37 };
38
39 # ── Zig 0.16.0 (fetched from upstream) ──────────────────────────────
40 zigVersion = "0.16.0";
41
42 zigBySystem = {
43 "x86_64-linux" = { plat = "x86_64-linux"; sha256 = "70e49664a74374b48b51e6f3fdfbf437f6395d42509050588bd49abe52ba3d00"; };
44 "aarch64-linux" = { plat = "aarch64-linux"; sha256 = "ea4b09bfb22ec6f6c6ceac57ab63efb6b46e17ab08d21f69f3a48b38e1534f17"; };
45 "x86_64-darwin" = { plat = "x86_64-macos"; sha256 = "0387557ed1877bc6a2e1802c8391953baddba76081876301c522f52977b52ba7"; };
46 "aarch64-darwin" = { plat = "aarch64-macos"; sha256 = "b23d70deaa879b5c2d486ed3316f7eaa53e84acf6fc9cc747de152450d401489"; };
47 };
48
49 zigInfo = zigBySystem.${system};
50
51 zig = pkgs.stdenv.mkDerivation {
52 pname = "zig";
53 version = zigVersion;
54 src = pkgs.fetchurl {
55 url = "https://ziglang.org/download/${zigVersion}/zig-${zigInfo.plat}-${zigVersion}.tar.xz";
56 sha256 = zigInfo.sha256;
57 };
58 dontConfigure = true;
59 dontBuild = true;
60 installPhase = ''
61 mkdir -p $out/bin $out/lib
62 cp -r lib $out/lib/zig
63 cp zig $out/bin/zig
64 '';
65 meta = with lib; {
66 description = "Zig ${zigVersion} compiler and toolchain";
67 homepage = "https://ziglang.org";
68 license = licenses.mit;
69 platforms = builtins.attrNames zigBySystem;
70 };
71 };
72
73 # ── Platform-specific packages ───────────────────────────────────────
74
75 # Linux: ALSA (cpal), D-Bus, libunwind — linked into rockboxd.
76 linuxPkgs = lib.optionals pkgs.stdenv.isLinux (with pkgs; [
77 alsa-lib alsa-lib.dev
78 dbus dbus.dev
79 libunwind libunwind.dev
80 ]);
81
82 # macOS: llvm-objcopy for codec --redefine-sym inside build-headless.sh.
83 # Use .llvm (not .bintools — bintools wraps Apple ld and needs the
84 # removed apple_sdk_11_0 stub).
85 darwinPkgs = lib.optionals pkgs.stdenv.isDarwin (with pkgs; [
86 llvmPackages_18.llvm
87 ]);
88
89 # macOS SDK sysroot for Zig's final link. The C/Rust builds use the
90 # nix cc wrapper (which injects -isysroot), but the `zig build` link
91 # step bypasses it and, with no `xcrun` in the sandbox, has an empty
92 # framework/lib search path — so linking CoreFoundation/CoreAudio/…
93 # fails with "unable to find framework". build.zig's -Dmacos-sdk adds
94 # `-F <sdk>/System/Library/Frameworks` + `-L <sdk>/usr/lib` (the SDK's
95 # .tbd stubs + libSystem). Lazy, so pkgs.apple-sdk is never forced on
96 # Linux.
97 appleSdkRoot = lib.optionalString pkgs.stdenv.isDarwin
98 "${pkgs.apple-sdk}/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk";
99
100 # ── PKG_CONFIG_PATH / LD_LIBRARY_PATH helpers (devShell only) ────────
101
102 pkgConfigDirs = lib.concatStringsSep ":" (
103 [
104 "${pkgs.SDL2.dev}/lib/pkgconfig"
105 "${pkgs.freetype.dev}/lib/pkgconfig"
106 "${pkgs.zlib.dev}/lib/pkgconfig"
107 "${pkgs.libusb1.dev}/lib/pkgconfig"
108 ]
109 ++ lib.optionals pkgs.stdenv.isLinux [
110 "${pkgs.alsa-lib.dev}/lib/pkgconfig"
111 "${pkgs.dbus.dev}/lib/pkgconfig"
112 "${pkgs.libunwind.dev}/lib/pkgconfig"
113 ]
114 );
115
116 ldLibDirs = lib.concatStringsSep ":" (
117 [ "${pkgs.SDL2}/lib" "${pkgs.freetype}/lib" "${pkgs.zlib}/lib" ]
118 ++ lib.optionals pkgs.stdenv.isLinux [
119 "${pkgs.alsa-lib}/lib"
120 "${pkgs.dbus}/lib"
121 "${pkgs.libunwind}/lib"
122 ]
123 );
124
125 # ── Build source (scoped + split) ────────────────────────────────────
126 # `src = ./.` rehashes the whole repo, so editing docs/CI/mobile apps
127 # busts the (heavy) derivations and defeats the binary cache. Split the
128 # inputs so each derivation only depends on what it actually reads:
129 # rustSrc — the cargo workspace (manifests, all members, deno/rmpc
130 # submodules). Feeds cargoDeps, the rockbox CLI, and the
131 # separately-cached Rust staticlibs.
132 # fwSrc — the firmware(make)+zig half: everything else the build
133 # reads, minus docs/CI/frontends AND minus the rust tree
134 # (so a Rust edit doesn't rebuild the firmware inputs, and
135 # the big deno tree stays out of the firmware derivation).
136 srcExcludes = lib.fileset.unions [
137 ./.github
138 ./flake.nix
139 ./flake.lock
140 ./expo
141 ./gpui
142 ./bindings
143 ./doc
144 ./docs
145 ./manual
146 ./mintlify
147 ./memory
148 ./.devcontainer
149 ./.fluentci
150 ./dagger.json
151 ./README.md
152 ./CHANGELOG.md
153 ./CLAUDE.md
154 ./CODE_OF_CONDUCT.md
155 ./CONTRIBUTING.md
156 ./AUDIO_SETTINGS.md
157 ./HEADLESS.md
158 ./SNAPCAST.md
159 ./THREADING.md
160 ./WEBASSEMBLY.md
161 ];
162 rustFileset = lib.fileset.unions [
163 ./Cargo.toml ./Cargo.lock
164 ./crates ./cli ./gtk ./webui ./deno ./rmpc
165 # [patch.crates-io] hyper-rustls = { path = "vendor/hyper-rustls" }
166 ./vendor
167 ];
168 rustSrc = lib.fileset.toSource {
169 root = ./.;
170 fileset = rustFileset;
171 };
172 fwSrc = lib.fileset.toSource {
173 root = ./.;
174 fileset = lib.fileset.difference
175 (lib.fileset.difference ./. srcExcludes)
176 rustFileset;
177 };
178
179 # ── WebUI static assets ──────────────────────────────────────────────
180 # Compiled from webui/rockbox/ and embedded by rockbox-server.
181 #
182 # To obtain / update npmDepsHash:
183 # nix build .#webui-assets 2>&1 | grep 'got:'
184 # then paste the printed hash below.
185 webuiAssets = pkgs.buildNpmPackage {
186 pname = "rockbox-webui";
187 version = "0.1.0";
188 src = ./webui/rockbox;
189
190 npmDepsHash = "sha256-zJxCDqddiRmZ7EFGtEBXPkr+A5Yq1Wtk2YQnFf+NMWQ=";
191
192 # --legacy-peer-deps: the lockfile pins graphql@15.7.2 while
193 # graphql-ws wants graphql@^15.10.1; bun/deno tolerate this,
194 # npm's strict peer resolution does not.
195 # --ignore-scripts: the `build` script produces the web dist via
196 # vite; electron is only a devDependency for the desktop variant
197 # and its postinstall tries to download a binary over the network
198 # (blocked in the sandbox). None of the web-build deps need
199 # install scripts.
200 npmFlags = [ "--legacy-peer-deps" "--ignore-scripts" ];
201
202 # Only the compiled dist/ is needed; skip npm's default pack step.
203 installPhase = ''
204 runHook preInstall
205 mkdir -p $out
206 cp -r dist/. $out/
207 runHook postInstall
208 '';
209 };
210
211 # ── S3 admin WebUI static assets ─────────────────────────────────────
212 # Compiled from crates/s3/s3webui/ and embedded by rockbox-s3
213 # (rust-embed folder $CARGO_MANIFEST_DIR/s3webui/dist in
214 # crates/s3/src/admin.rs). rockbox-s3 is a dependency of rockbox-server,
215 # so these assets must exist before the Rust build.
216 #
217 # To obtain / update npmDepsHash:
218 # nix build .#s3webui-assets 2>&1 | grep 'got:'
219 # then paste the printed hash below.
220 s3webuiAssets = pkgs.buildNpmPackage {
221 pname = "rockbox-s3-webui";
222 version = "0.0.0";
223 src = lib.fileset.toSource {
224 root = ./crates/s3/s3webui;
225 # Full source tree needed for `vite build`. Only the checked-in
226 # inputs are listed; generated/vendored dirs (node_modules, dist,
227 # .tanstack) are gitignored and deliberately excluded.
228 fileset = lib.fileset.unions [
229 ./crates/s3/s3webui/package.json
230 ./crates/s3/s3webui/package-lock.json
231 ./crates/s3/s3webui/index.html
232 ./crates/s3/s3webui/vite.config.ts
233 ./crates/s3/s3webui/tsconfig.json
234 ./crates/s3/s3webui/tsconfig.app.json
235 ./crates/s3/s3webui/tsconfig.node.json
236 ./crates/s3/s3webui/tsr.config.json
237 ./crates/s3/s3webui/.oxlintrc.json
238 ./crates/s3/s3webui/src
239 ./crates/s3/s3webui/public
240 ];
241 };
242
243 npmDepsHash = "sha256-FKkuvP7JfewxGCe8WUjZlDgFSL48ll5/3WE4lGMdE/w=";
244
245 # Only the compiled dist/ is needed; skip npm's default pack step.
246 installPhase = ''
247 runHook preInstall
248 mkdir -p $out
249 cp -r dist/. $out/
250 runHook postInstall
251 '';
252 };
253
254 # ── Vendored Cargo sources ────────────────────────────────────────────
255 # fetchCargoVendor runs `cargo vendor` once and caches the result.
256 # Single hash covers the entire workspace including all transitive deps.
257 #
258 # To obtain / update the hash:
259 # nix build .#rockboxd 2>&1 | grep 'got:'
260 # then paste the printed hash below.
261 cargoDeps = pkgs.rustPlatform.fetchCargoVendor {
262 src = rustSrc;
263 hash = "sha256-xp2nWa6nX2Q2tV8q1cpvPHEgHPMLaJB3PFI6CaFrRE8=";
264 };
265
266 # ── Rust staticlibs (separately cached) ──────────────────────────────
267 # librockbox_cli.a + librockbox_server.a, built in their own derivation
268 # (src = rustSrc) so the ~5-min Rust compile is cached independently of
269 # the firmware/zig link and shared via the binary cache. rockboxd's
270 # build-headless.sh consumes these with SKIP_CARGO=1. Features mirror
271 # scripts/build-headless.sh: Linux and macOS both use the default
272 # cpal-sink + Typesense (no fts5). fts5 is a BSD-only fallback there —
273 # and the flake doesn't target the BSDs — so it never applies here.
274 rustCliFeatures = "cpal-sink";
275 rustServerFeatures = "";
276 rockboxRustLibs = pkgs.stdenv.mkDerivation {
277 pname = "rockbox-rustlibs";
278 version = "0.1.0";
279 src = rustSrc;
280
281 nativeBuildInputs = with pkgs; [
282 rustToolchain
283 gnumake
284 gcc
285 pkg-config
286 cmake
287 perl
288 python3
289 protobuf # protoc for tonic build.rs
290 rustPlatform.cargoSetupHook
291 ] ++ darwinPkgs;
292
293 # -sys crates' build scripts need these at compile time (alsa/dbus on
294 # Linux for the sink features; zlib for flate2/libz-sys).
295 buildInputs = with pkgs; [
296 zlib zlib.dev
297 ] ++ linuxPkgs;
298
299 inherit cargoDeps;
300
301 dontUseCmakeConfigure = true;
302
303 # rockbox-server / rockbox-s3 embed the compiled web UIs via rust-embed
304 # at compile time, so the dist/ dirs must exist before cargo runs.
305 preBuild = ''
306 mkdir -p webui/rockbox/dist
307 cp -r ${webuiAssets}/. webui/rockbox/dist/
308 mkdir -p crates/s3/s3webui/dist
309 cp -r ${s3webuiAssets}/. crates/s3/s3webui/dist/
310 '';
311
312 buildPhase = ''
313 runHook preBuild
314 cargo build --release --features "${rustCliFeatures}" -p rockbox-cli
315 ${if rustServerFeatures != "" then
316 ''cargo build --release --features "${rustServerFeatures}" -p rockbox-server''
317 else
318 ''cargo build --release -p rockbox-server''}
319 runHook postBuild
320 '';
321
322 installPhase = ''
323 runHook preInstall
324 mkdir -p $out/lib
325 cp target/release/librockbox_cli.a $out/lib/
326 cp target/release/librockbox_server.a $out/lib/
327 runHook postInstall
328 '';
329 };
330
331 # ── Prebuilt V8 for the `deno` crate (v8 / rusty_v8 130.0.2) ──────────
332 # cli/ (package `rockbox`) depends on the `v8` crate, whose build.rs
333 # downloads a prebuilt librusty_v8 archive from GitHub — blocked in the
334 # nix sandbox. Fetch it here and hand it to the crate via
335 # RUSTY_V8_ARCHIVE; RUSTY_V8_SRC_BINDING_PATH supplies the matching
336 # prebuilt bindings so the build skips bindgen/libclang entirely.
337 #
338 # To refresh after a v8 bump: read the version from Cargo.lock, then
339 # nix store prefetch-file <release-url> --json | jq -r .hash
340 rustyV8Version = "130.0.2";
341 rustyV8BySystem = {
342 "x86_64-linux" = { triple = "x86_64-unknown-linux-gnu"; archiveHash = "sha256-ew2WZhdsHfffRQtif076AWAlFohwPo/RbmW/6D3LzkU="; bindingHash = "sha256-vbWjlLdQaqz5kBgL0XnrwhhdsPrrdHd1Q54YlxFmYKM="; };
343 "aarch64-linux" = { triple = "aarch64-unknown-linux-gnu"; archiveHash = "sha256-p9+tHmKIM5wBABubHIAstpwfzO19ypPzOuaV4b6loCU="; bindingHash = "sha256-vbWjlLdQaqz5kBgL0XnrwhhdsPrrdHd1Q54YlxFmYKM="; };
344 "x86_64-darwin" = { triple = "x86_64-apple-darwin"; archiveHash = "sha256-zNC0DAkMbbFM1M+t6rgKtN0QAm4ONEbCi6Sxivhf8dk="; bindingHash = "sha256-ZJlJ9b4kNwzsQrAfMrtqLc5v2f9M1QB1DsiwNlfiIbw="; };
345 "aarch64-darwin" = { triple = "aarch64-apple-darwin"; archiveHash = "sha256-aWZ/4Q4Wttx37xOdBmTCPGP+eYGhr4CM1UkYq8pC7Qs="; bindingHash = "sha256-ZJlJ9b4kNwzsQrAfMrtqLc5v2f9M1QB1DsiwNlfiIbw="; };
346 };
347 rustyV8 = rustyV8BySystem.${system};
348 rustyV8Url = kind: "https://github.com/denoland/rusty_v8/releases/download/v${rustyV8Version}/${kind}_release_${rustyV8.triple}";
349 # rusty_v8 ships the lib gzip-compressed; RUSTY_V8_ARCHIVE wants the
350 # decompressed .a, so gunzip it into a fixed store path.
351 rustyV8Archive = pkgs.runCommand "librusty_v8_release_${rustyV8.triple}.a" { } ''
352 ${pkgs.gzip}/bin/gzip -dc ${pkgs.fetchurl {
353 url = "${rustyV8Url "librusty_v8"}.a.gz";
354 hash = rustyV8.archiveHash;
355 }} > $out
356 '';
357 rustyV8Binding = pkgs.fetchurl {
358 url = "${rustyV8Url "src_binding"}.rs";
359 hash = rustyV8.bindingHash;
360 };
361
362 # ── rockboxd derivation ───────────────────────────────────────────────
363 # Build order mirrors scripts/build-headless.sh, minus cargo:
364 # 1. configure + make lib (headless C firmware)
365 # 2. (cargo skipped — prebuilt Rust staticlibs injected below)
366 # 3. zig build (final link)
367 rockboxd = pkgs.stdenv.mkDerivation {
368 pname = "rockboxd";
369 version = "0.1.0";
370 src = fwSrc;
371
372 nativeBuildInputs = with pkgs; [
373 zig
374 gnumake
375 gcc
376 pkg-config
377 cmake
378 perl # tools/configure is a Perl script
379 python3
380 zip # firmware build packages voice/lang zips (tools/buildzip.pl)
381 unzip
382 makeWrapper # wrap rockboxd so typesense-server is on its PATH
383 ] ++ darwinPkgs;
384
385 # Libraries linked into the final binary.
386 buildInputs = with pkgs; [
387 freetype freetype.dev
388 zlib zlib.dev
389 libusb1 libusb1.dev
390 ] ++ linuxPkgs;
391
392 # Nixpkgs' stdenv injects -Werror=format-security via the "format"
393 # hardening flag. Rockbox's splash()/splashf() are printf-style and
394 # are routinely called with a runtime format pointer (e.g.
395 # `splash(HZ/2, ID2P(LANG_TIMEOUT))`), which trips that check and
396 # turns every such call into a hard error. Upstream Rockbox never
397 # sets this flag; disable it so the firmware compiles as designed.
398 # "fortify" too: the macOS SDK's <string.h> turns strlcpy/strlcat into
399 # __builtin___str*_chk fortify macros at _USE_FORTIFY_LEVEL>0, which
400 # mangle Rockbox's own strlcpy.c/strlcat.c definitions and call sites.
401 hardeningDisable = [ "format" "fortify" ];
402
403 # macOS defaults _USE_FORTIFY_LEVEL to 2 in the SDK even without the
404 # nixpkgs hardening flag, so force it off explicitly for the firmware
405 # C compile (the nix cc-wrapper appends NIX_CFLAGS_COMPILE; zig has
406 # its own driver and ignores it). Restores the pre-26.05 behavior.
407 NIX_CFLAGS_COMPILE = lib.optionalString pkgs.stdenv.isDarwin "-D_FORTIFY_SOURCE=0";
408
409 # cmake is present for sub-builds that need it, but the top level
410 # has no CMakeLists.txt — rockboxd builds via make + zig
411 # (scripts/build-headless.sh). Skip cmake's default configurePhase.
412 dontUseCmakeConfigure = true;
413
414 # On macOS, hand Zig's link step the SDK framework/lib dirs (see
415 # appleSdkRoot). build-headless.sh appends $ZIG_EXTRA_ARGS to
416 # `zig build`. Empty on Linux, so it's a harmless no-op there.
417 ZIG_EXTRA_ARGS = lib.optionalString pkgs.stdenv.isDarwin "-Dmacos-sdk=${appleSdkRoot}";
418
419 # The Rockbox build invokes helper scripts under tools/ (genlang,
420 # *.pl, *.py) whose shebangs hardcode /usr/bin/perl and /usr/bin/python,
421 # absent in the nix sandbox. Rewrite them to the nativeBuildInputs
422 # interpreters so `make` can execute them.
423 postPatch = ''
424 patchShebangs tools
425 '';
426
427 # Inject the separately-built Rust staticlibs where zig's link step
428 # (build.zig → ../target/release) expects them; SKIP_CARGO=1 then
429 # tells build-headless.sh not to rebuild them.
430 preBuild = ''
431 mkdir -p target/release
432 cp ${rockboxRustLibs}/lib/librockbox_cli.a target/release/
433 cp ${rockboxRustLibs}/lib/librockbox_server.a target/release/
434 '';
435
436 buildPhase = ''
437 runHook preBuild
438 export ZIG_GLOBAL_CACHE_DIR="$TMPDIR/zig-cache"
439 export ZIG_LOCAL_CACHE_DIR="$TMPDIR/zig-cache"
440 SKIP_CARGO=1 bash scripts/build-headless.sh
441 runHook postBuild
442 '';
443
444 installPhase = ''
445 runHook preInstall
446 mkdir -p $out/bin
447 cp zig/zig-out/bin/rockboxd $out/bin/rockboxd
448 runHook postInstall
449 '';
450
451 # rockboxd spawns `typesense-server` as a subprocess for the search
452 # index (crates/cli/src/lib.rs), falling back to PATH lookup when
453 # ~/.rockbox/bin/typesense-server is absent. Put typesense on PATH so
454 # an installed rockboxd works out of the box.
455 postInstall = ''
456 wrapProgram $out/bin/rockboxd \
457 --prefix PATH : ${lib.makeBinPath [ pkgs.typesense ]}
458 '';
459
460 meta = with lib; {
461 description = "Rockbox daemon — gRPC / GraphQL / HTTP / MPD audio server";
462 homepage = "https://github.com/tsirysndr/rockboxd";
463 license = licenses.lgpl21;
464 platforms = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ];
465 mainProgram = "rockboxd";
466 };
467 };
468
469 # ── rockbox CLI derivation ────────────────────────────────────────────
470 # The `rockbox` gRPC client / CLI (cli/, cargo package "rockbox").
471 # Pure Rust (tonic client, no C firmware linkage) — its build.rs only
472 # runs tonic_build protoc codegen. Depends on the `deno` (deno/cli) and
473 # `rmpc` (rmpc/) path crates, both git submodules carried into the
474 # source via inputs.self.submodules; their transitive registry deps are
475 # already covered by cargoDeps.
476 rockbox = pkgs.stdenv.mkDerivation {
477 pname = "rockbox";
478 version = "0.1.0";
479 src = rustSrc;
480
481 nativeBuildInputs = with pkgs; [
482 rustToolchain
483 pkg-config
484 cmake
485 perl
486 python3
487 protobuf # protoc for tonic_build codegen
488 # Wires up offline Cargo registry from cargoDeps.
489 rustPlatform.cargoSetupHook
490 ] ++ darwinPkgs;
491
492 # Native libs pulled in by the deno extensions (libffi → deno_ffi,
493 # zlib → __vendored_zlib_ng / flate2).
494 buildInputs = with pkgs; [
495 zlib zlib.dev
496 libffi libffi.dev
497 ] ++ linuxPkgs;
498
499 inherit cargoDeps;
500
501 # cmake is present for native deps that use it, but the workspace
502 # root has no CMakeLists.txt — skip cmake's default configurePhase.
503 dontUseCmakeConfigure = true;
504
505 # Use the pre-fetched V8 static lib + bindings instead of letting the
506 # v8 crate download them at build time (no network in the sandbox).
507 RUSTY_V8_ARCHIVE = rustyV8Archive;
508 RUSTY_V8_SRC_BINDING_PATH = rustyV8Binding;
509
510 buildPhase = ''
511 runHook preBuild
512 cargo build -p rockbox --release
513 runHook postBuild
514 '';
515
516 installPhase = ''
517 runHook preInstall
518 mkdir -p $out/bin
519 cp target/release/rockbox $out/bin/rockbox
520 runHook postInstall
521 '';
522
523 meta = with lib; {
524 description = "rockbox — gRPC client / CLI for the Rockbox daemon";
525 homepage = "https://github.com/tsirysndr/rockboxd";
526 license = licenses.lgpl21;
527 platforms = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ];
528 mainProgram = "rockbox";
529 };
530 };
531
532 in
533 {
534 # ── packages ────────────────────────────────────────────────────────────
535 # nix build / nix shell / nix profile install all use packages.default.
536 packages = {
537 default = rockboxd; # ← what gets installed
538 inherit rockboxd rockbox;
539 rockbox-rustlibs = rockboxRustLibs; # cached separately to speed rebuilds
540 webui-assets = webuiAssets; # exposed separately to ease hash updates
541 s3webui-assets = s3webuiAssets; # exposed separately to ease hash updates
542 };
543
544 # ── nix develop ─────────────────────────────────────────────────────────
545 devShells.default = pkgs.mkShell {
546 packages = with pkgs; [
547 zig
548 rustToolchain
549 gnumake
550 gcc
551 pkg-config
552 cmake
553 perl
554 python3
555 zip # firmware build packages voice/lang zips (tools/buildzip.pl)
556 unzip
557 SDL2 SDL2.dev
558 freetype freetype.dev
559 zlib zlib.dev
560 libusb1 libusb1.dev
561 protobuf
562 buf
563 grpcurl
564 evans
565 typesense # rockboxd spawns typesense-server as a subprocess
566 bun
567 deno
568 # tools/console — babashka runs bb.edn tasks, clojure runs the
569 # deps.edn REPL aliases (clj -M:rebel / nREPL); both need a JDK.
570 jdk
571 clojure
572 babashka
573 ] ++ linuxPkgs ++ darwinPkgs;
574
575 shellHook = ''
576 echo "Rockbox Daemon development environment"
577 echo " Zig: $(zig version)"
578 echo " Rust: $(rustc --version)"
579 echo ""
580 echo "Headless build (cpal, no SDL):"
581 echo " cd webui/rockbox && deno install --allow-scripts && deno task build && cd ../.."
582 echo " bash scripts/build-headless.sh"
583 echo ""
584 echo "SDL build:"
585 echo " cd webui/rockbox && deno install --allow-scripts && deno task build && cd ../.."
586 echo " cd build-lib && make lib -j\$(nproc)"
587 echo " cargo build --release -p rockbox-cli -p rockbox-server"
588 echo " cd zig && zig build"
589
590 export PKG_CONFIG_PATH="${pkgConfigDirs}"
591 export ZIG_GLOBAL_CACHE_DIR="$PWD/.zig-cache"
592 export ZIG_LOCAL_CACHE_DIR="$PWD/.zig-cache"
593 '' + lib.optionalString pkgs.stdenv.isLinux ''
594 export LD_LIBRARY_PATH="${ldLibDirs}"
595 '' + lib.optionalString pkgs.stdenv.isDarwin ''
596 export DYLD_LIBRARY_PATH="${pkgs.SDL2}/lib:${pkgs.freetype}/lib:${pkgs.zlib}/lib"
597 export ROCKBOX_LLVM_OBJCOPY="$(command -v llvm-objcopy 2>/dev/null)"
598 '';
599 };
600
601 # ── nix run .#<name> convenience scripts ─────────────────────────────
602 apps = {
603 # Full headless build: webui → firmware → Rust → Zig
604 build-headless = {
605 type = "app";
606 program = "${pkgs.writeShellScript "build-headless" ''
607 set -euo pipefail
608 echo "==> Step 0: WebUI"
609 (cd webui/rockbox && deno install --allow-scripts && deno task build)
610 exec bash scripts/build-headless.sh "$@"
611 ''}";
612 };
613
614 # SDL build: webui → make lib → cargo → zig build
615 build-sdl = {
616 type = "app";
617 program = "${pkgs.writeShellScript "build-sdl" ''
618 set -euo pipefail
619 NCPU=$(nproc 2>/dev/null || sysctl -n hw.logicalcpu 2>/dev/null || echo 4)
620 echo "==> Step 0: WebUI"
621 (cd webui/rockbox && deno install --allow-scripts && deno task build)
622 echo "==> Step 1: firmware (build-lib)"
623 (cd build-lib && make lib -j"$NCPU")
624 echo "==> Step 2: Rust crates"
625 cargo build --release -p rockbox-cli -p rockbox-server
626 echo "==> Step 3: Zig link"
627 (cd zig && zig build)
628 echo "Done: zig/zig-out/bin/rockboxd"
629 ''}";
630 };
631
632 # `nix run` (no attr) runs the daemon; the built binary is wrapped so
633 # typesense-server is on its PATH.
634 default = {
635 type = "app";
636 program = "${rockboxd}/bin/rockboxd";
637 };
638 };
639 }
640 );
641}