diff --git a/README.md b/README.md index 7030d26..7f799df 100644 --- a/README.md +++ b/README.md @@ -271,6 +271,38 @@ 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); diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..83459d4 --- /dev/null +++ b/flake.lock @@ -0,0 +1,116 @@ +{ + "nodes": { + "crane": { + "locked": { + "lastModified": 1784564969, + "narHash": "sha256-TkTWNhJV/8Wk3czlbz4bwHZkFCaCHPsOy+oJe4PUiXg=", + "owner": "ipetkov", + "repo": "crane", + "rev": "7930f6c291de6f83c257839d434592aa085f290a", + "type": "github" + }, + "original": { + "owner": "ipetkov", + "repo": "crane", + "type": "github" + } + }, + "fenix": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ], + "rust-analyzer-src": "rust-analyzer-src" + }, + "locked": { + "lastModified": 1784795840, + "narHash": "sha256-Z5cDTnG/+k7lNMMyuNH2RDWmSuGw2d3nV9AlVrpGnNE=", + "owner": "nix-community", + "repo": "fenix", + "rev": "85164e3aaadbc35cbbb4a9fef6d358b607da09fc", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "fenix", + "type": "github" + } + }, + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1784555310, + "narHash": "sha256-/FCliTPgiuV1owejZFNx3Ch9irdvkOfOFl+HHZ+DrtM=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "421eebfd0ec7bccd4abe826ce62d7e6e83129493", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "crane": "crane", + "fenix": "fenix", + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + }, + "rust-analyzer-src": { + "flake": false, + "locked": { + "lastModified": 1784667699, + "narHash": "sha256-853teJQOgTTKRfNxcIJ53ziJOGNeg3c74PvQl5l61Jc=", + "owner": "rust-lang", + "repo": "rust-analyzer", + "rev": "1174734d9c6453d13d2b5e3d578512c579ca37f1", + "type": "github" + }, + "original": { + "owner": "rust-lang", + "ref": "nightly", + "repo": "rust-analyzer", + "type": "github" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..34b15e5 --- /dev/null +++ b/flake.nix @@ -0,0 +1,149 @@ +{ + description = "crabidy — a client/server music player"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; + crane.url = "github:ipetkov/crane"; + fenix = { + url = "github:nix-community/fenix"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + flake-utils.url = "github:numtide/flake-utils"; + }; + + outputs = + { + self, + nixpkgs, + crane, + fenix, + flake-utils, + }: + flake-utils.lib.eachDefaultSystem ( + system: + let + pkgs = import nixpkgs { inherit system; }; + lib = pkgs.lib; + fenixPkgs = fenix.packages.${system}; + + # Source that keeps the .proto files crabidy-core/build.rs compiles — + # crane's default cargo-source cleaning would drop them. + src = lib.cleanSourceWith { + src = ./.; + name = "crabidy-source"; + filter = + path: type: + (builtins.match ".*\\.proto$" path != null) || (crane.mkLib pkgs).filterCargoSources path type; + }; + + # Only the real binary crates; never build cbd-web (a wasm crate) for a + # host or aarch64 target. Headless: --no-default-features drops the + # embedded web UI (its own wasm-bundle derivation is future work). + workspaceBins = "-p crabidy-server -p cbd -p cbd-tui"; + + # --- Native toolchain / build (for `nix profile install` on x86_64, + # aarch64, etc.) --- + craneLib = (crane.mkLib pkgs).overrideToolchain (_: fenixPkgs.stable.toolchain); + + nativeArgs = { + inherit src; + strictDeps = true; + cargoExtraArgs = "--locked --no-default-features ${workspaceBins}"; + # protoc + pkg-config are build-host tools (run during the build); + # with strictDeps they must be nativeBuildInputs or alsa-sys cannot + # find pkg-config. alsa-lib is the actual linked library. + nativeBuildInputs = [ + pkgs.protobuf + pkgs.pkg-config + ]; + buildInputs = [ pkgs.alsa-lib ]; + # audio-player links libasound; yt-dlp for /youtube stays a runtime + # dependency, not wired here. + }; + nativeDeps = craneLib.buildDepsOnly nativeArgs; + crabidy = craneLib.buildPackage ( + nativeArgs + // { + cargoArtifacts = nativeDeps; + doCheck = false; # tests need a config/network; `nix flake check` can run them later + } + ); + + # --- Cross toolchain / build: static aarch64 musl (a portable binary + # that runs on any aarch64 Linux, incl. a Raspberry Pi) --- + muslTarget = "aarch64-unknown-linux-musl"; + crossPkgs = pkgs.pkgsCross.aarch64-multiplatform-musl; + crossCc = "${crossPkgs.stdenv.cc}/bin/${crossPkgs.stdenv.cc.targetPrefix}cc"; + # Static alsa-lib for the target, linked into the binary. + crossAlsa = crossPkgs.pkgsStatic.alsa-lib; + + crossToolchain = fenixPkgs.combine [ + fenixPkgs.stable.rustc + fenixPkgs.stable.cargo + fenixPkgs.targets.${muslTarget}.stable.rust-std + ]; + craneLibCross = (crane.mkLib pkgs).overrideToolchain (_: crossToolchain); + + crossEnvTarget = "AARCH64_UNKNOWN_LINUX_MUSL"; + crossArgs = { + inherit src; + strictDeps = true; + cargoExtraArgs = "--locked --no-default-features -p crabidy-server"; + CARGO_BUILD_TARGET = muslTarget; + + # Build scripts / proc-macros compile for the build host; deps for the + # target use the cross toolchain. protoc + pkg-config are host tools. + nativeBuildInputs = [ + pkgs.protobuf + pkgs.pkg-config + ]; + buildInputs = [ crossAlsa ]; + depsBuildBuild = [ crossPkgs.stdenv.cc ]; + + # cc-rs (aws-lc-sys, rustls's crypto backend) needs the cross C + # compiler; the linker is the same cross cc. Static CRT + -static so + # the binary has no interpreter and runs standalone. + "CC_aarch64_unknown_linux_musl" = crossCc; + "CARGO_TARGET_${crossEnvTarget}_LINKER" = crossCc; + "CARGO_TARGET_${crossEnvTarget}_RUSTFLAGS" = "-C target-feature=+crt-static -C link-arg=-static"; + + # pkg-config resolves the target's static alsa-lib for alsa-sys. + PKG_CONFIG_ALLOW_CROSS = "1"; + PKG_CONFIG_PATH_aarch64_unknown_linux_musl = "${crossAlsa.dev}/lib/pkgconfig"; + }; + crossDeps = craneLibCross.buildDepsOnly crossArgs; + crabidy-server-aarch64 = craneLibCross.buildPackage ( + crossArgs + // { + cargoArtifacts = crossDeps; + doCheck = false; # cannot run aarch64 tests on an x86_64 builder + } + ); + in + { + packages = { + default = crabidy; + inherit crabidy crabidy-server-aarch64; + }; + + apps = { + cbd = flake-utils.lib.mkApp { + drv = crabidy; + name = "cbd"; + }; + cbd-tui = flake-utils.lib.mkApp { + drv = crabidy; + name = "cbd-tui"; + }; + crabidy-server = flake-utils.lib.mkApp { + drv = crabidy; + name = "crabidy-server"; + }; + default = flake-utils.lib.mkApp { + drv = crabidy; + name = "cbd"; + }; + }; + } + ); +}