1{ 2 description = "whiskers: Rust core + Android shell for a talking cat on a Fire tablet"; 3 4 inputs = { 5 nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; 6 # Rust std for the Android targets, as the dashboard takes the Windows one. 7 fenix.url = "github:nix-community/fenix"; 8 fenix.inputs.nixpkgs.follows = "nixpkgs"; 9 }; 10 11 outputs = 12 { nixpkgs, fenix, ... }: 13 let 14 system = "x86_64-linux"; 15 pkgs = nixpkgs.legacyPackages.${system};
The SDK is unfree and its licence needs accepting; keep that out of the plain pkgs.
Same SDK composition as ~/dashboard's devshell (so the Gradle build is the one already proven there), plus the NDK for the Rust core.
The same, plus an emulator and an x86_64 system image, to run the app for real on this machine (it has /dev/kvm) without a phone.
40 androidEmu = androidPkgs.androidenv.composeAndroidPackages { 41 platformVersions = [ "37" "35" ]; 42 buildToolsVersions = [ "36.0.0" ]; 43 includeNDK = true; 44 includeEmulator = true; 45 includeSystemImages = true; 46 systemImageTypes = [ "google_apis" ]; 47 abiVersions = [ "x86_64" ]; 48 includeSources = false; 49 cmdLineToolsVersion = "13.0"; 50 };
Her tablet is 32- and 64-bit (Amazon's Fire HD 10 specifications, 2026-10-04): build both so the choice is not a guess about which userland Fire OS runs.
54 rust = fenix.packages.${system}.combine [ 55 fenix.packages.${system}.stable.toolchain 56 fenix.packages.${system}.targets.aarch64-linux-android.stable.rust-std 57 fenix.packages.${system}.targets.armv7-linux-androideabi.stable.rust-std 58 # Only for the emulator. 59 fenix.packages.${system}.targets.x86_64-linux-android.stable.rust-std 60 # So the portable crates (ports, service) can be checked for a Worker: 61 # `cargo check --target wasm32-unknown-unknown -p whiskers-ports -p whiskers-service`. 62 fenix.packages.${system}.targets.wasm32-unknown-unknown.stable.rust-std 63 ];
The website (web/): a Rust Cloudflare Worker, as ~/lmjtfy builds one. Rust for wasm only, without the Android targets above, plus what turns the cat into the link-preview PNG and MP4 (resvg, ffmpeg).
celld, Deno Land's self-hosted Durable Objects host (Apache-2.0), from its release binary: the
asset's sha256 is the one GitHub publishes for it, and the build attests to commit f2bf648
(gh attestation verify celld-x86_64-unknown-linux-gnu.gz --repo denoland/celld, 2026-10-05).
Not in nixpkgs. Used by celld dev for backend/worker.
77 celld = pkgs.stdenv.mkDerivation rec { 78 pname = "celld"; 79 version = "0.6.1"; 80 src = pkgs.fetchurl { 81 url = "https://github.com/denoland/celld/releases/download/v${version}/celld-x86_64-unknown-linux-gnu.gz"; 82 hash = "sha256-eaglPP9dTopKn3omEeOTOQ9/6QJfAOiEZ4dbAHxEhms="; 83 }; 84 dontUnpack = true; 85 nativeBuildInputs = [ pkgs.autoPatchelfHook ]; 86 buildInputs = [ pkgs.stdenv.cc.cc.lib ]; 87 installPhase = '' 88 install -d $out/bin 89 gzip -dc $src > $out/bin/celld 90 chmod 755 $out/bin/celld 91 ''; 92 meta.mainProgram = "celld"; 93 };
whiskersd, the service the tablet talks to, built from the workspace so nixos-config can run it as a real service (modules/whiskers/service.nix there). Only what the build reads is in the source: a change to the Android app, the website or the docs leaves this derivation, and so the running service, alone.
The jevcrates submodule is in the source only when this flake is fetched with submodules, so a consumer takes
it as git+file:///home/nixos/whiskers?submodules=1 and a local build is nix build '.?submodules=1#whiskersd'.
Without them the build stops at the first path dependency instead of building something else.
102 mkWhiskersd = rustPlatform: rustPlatform.buildRustPackage { 103 pname = "whiskersd"; 104 version = "0.0.0"; 105 src = pkgs.lib.fileset.toSource { 106 root = ./.; 107 fileset = pkgs.lib.fileset.unions [ 108 ./Cargo.toml 109 ./Cargo.lock 110 ./crates 111 ./third-party 112 ]; 113 }; 114 cargoLock = { 115 lockFile = ./Cargo.lock; 116 # jevcrates takes rustls-rustcrypto from git (no release has the version it needs); the hash is the 117 # fetched tree, so it moves with the rev pinned in Cargo.lock. 118 outputHashes."rustls-rustcrypto-0.0.2-alpha" = "sha256-tkaRZgDoaP0cuajGcfNvayKh2xgXxhJ6YXLCrSZ3JUE="; 119 }; 120 cargoBuildFlags = [ "-p" "whiskersd" ]; 121 cargoTestFlags = [ "-p" "whiskersd" ]; 122 meta.mainProgram = "whiskersd"; 123 }; 124 whiskersd = mkWhiskersd pkgs.rustPlatform; 125 # The same, linked statically against musl, so one file runs on any x86-64 Linux with no nix store: this is 126 # what a release carries. `nix build '.?submodules=1#whiskersd-static'`. 127 whiskersd-static = mkWhiskersd pkgs.pkgsStatic.rustPlatform; 128 in 129 { 130 packages.${system} = { inherit celld whiskersd whiskersd-static; };
132 devShells.${system} = { 133 # The website: `nix develop ~/whiskers#web`. See web/README.md. 134 web = pkgs.mkShell { 135 packages = [ 136 webRust 137 pkgs.gcc 138 pkgs.worker-build 139 pkgs.wasm-bindgen-cli 140 pkgs.binaryen 141 pkgs.esbuild 142 pkgs.wrangler 143 pkgs.ffmpeg 144 pkgs.resvg 145 pkgs.curl 146 pkgs.jq 147 ]; 148 # worker-build otherwise downloads its own copies. The wasm-bindgen crate in 149 # web/Cargo.toml is pinned to this CLI's exact version: move them together. 150 WASM_BINDGEN_BIN = "${pkgs.wasm-bindgen-cli}/bin/wasm-bindgen"; 151 WASM_OPT_BIN = "${pkgs.binaryen}/bin/wasm-opt"; 152 ESBUILD_BIN = "${pkgs.esbuild}/bin/esbuild"; 153 WRANGLER_SEND_METRICS = "false"; 154 };
The backend Worker (backend/worker): worker-build and wrangler for Cloudflare's side, celld for the self-hosted side, one build for both. See backend/worker/README.md.
158 backend = pkgs.mkShell { 159 packages = [ 160 webRust 161 pkgs.gcc 162 pkgs.worker-build 163 pkgs.wasm-bindgen-cli 164 pkgs.binaryen 165 pkgs.esbuild 166 pkgs.wrangler 167 celld 168 pkgs.curl 169 pkgs.jq 170 ]; 171 # worker-build otherwise downloads its own copies. The wasm-bindgen crate in 172 # backend/worker/Cargo.toml is pinned to this CLI's exact version: move them together. 173 WASM_BINDGEN_BIN = "${pkgs.wasm-bindgen-cli}/bin/wasm-bindgen"; 174 WASM_OPT_BIN = "${pkgs.binaryen}/bin/wasm-opt"; 175 ESBUILD_BIN = "${pkgs.esbuild}/bin/esbuild"; 176 WRANGLER_SEND_METRICS = "false"; 177 };
Rust only: the core's tests and the services.
The android shell with an emulator and a system image (large; first use downloads them).
185 android-emu = pkgs.mkShell { 186 packages = [ 187 rust pkgs.gcc pkgs.cargo-ndk pkgs.curl pkgs.unzip pkgs.bzip2 pkgs.jdk21 pkgs.gradle_9 188 androidEmu.androidsdk androidEmu.platform-tools 189 ]; 190 JAVA_HOME = pkgs.jdk21.home; 191 ANDROID_HOME = "${androidEmu.androidsdk}/libexec/android-sdk"; 192 shellHook = '' 193 export ANDROID_SDK_ROOT="$ANDROID_HOME" 194 export ANDROID_NDK_HOME="$(ls -d $ANDROID_HOME/ndk/* | head -1)" 195 ''; 196 };
Everything for the tablet: Gradle, the SDK, the NDK and the Rust cross toolchain.
199 android = pkgs.mkShell { 200 packages = [ 201 rust 202 pkgs.gcc 203 pkgs.cargo-ndk 204 pkgs.curl 205 pkgs.unzip 206 pkgs.bzip2 207 pkgs.jdk21 208 pkgs.gradle_9 209 android.androidsdk 210 android.platform-tools 211 ]; 212 JAVA_HOME = pkgs.jdk21.home; 213 ANDROID_HOME = "${android.androidsdk}/libexec/android-sdk"; 214 shellHook = '' 215 export ANDROID_SDK_ROOT="$ANDROID_HOME" 216 export ANDROID_NDK_HOME="$(ls -d $ANDROID_HOME/ndk/* | head -1)" 217 ''; 218 }; 219 }; 220 }; 221}