Add a crane flake: native packages + static aarch64 cross build

`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>
This commit is contained in:
Test User 2026-07-23 13:13:08 +02:00
parent d078e65a7d
commit 89d5fffd48
3 changed files with 297 additions and 0 deletions

View File

@ -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);

116
flake.lock Normal file
View File

@ -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
}

149
flake.nix Normal file
View File

@ -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";
};
};
}
);
}