A tiny Subsonic/Jellyfin/S3 server in Rust
navidrome
subsonic
s3
emby
jellyfin
1{
2 description = "smolsonic - A tiny Subsonic-compatible music server";
3
4 inputs = {
5 nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
6
7 crane = {
8 url = "github:ipetkov/crane";
9 inputs.nixpkgs.follows = "nixpkgs";
10 };
11
12 fenix = {
13 url = "github:nix-community/fenix";
14 inputs.nixpkgs.follows = "nixpkgs";
15 inputs.rust-analyzer-src.follows = "";
16 };
17
18 flake-utils.url = "github:numtide/flake-utils";
19
20 advisory-db = {
21 url = "github:rustsec/advisory-db";
22 flake = false;
23 };
24 };
25
26 outputs = { self, nixpkgs, crane, fenix, flake-utils, advisory-db, ... }:
27 flake-utils.lib.eachDefaultSystem (system:
28 let
29 pkgs = import nixpkgs {
30 inherit system;
31 };
32
33 inherit (pkgs) lib;
34
35 craneLib = crane.mkLib pkgs;
36
37 src = craneLib.cleanCargoSource ./.;
38
39 # node_modules for the s3webui SPA. `bun install` needs the network,
40 # so dependency resolution lives in a fixed-output derivation.
41 #
42 # The tree is NOT platform-independent: native optional deps (e.g.
43 # @tailwindcss/oxide-*) are installed per-OS/CPU, so each system
44 # needs its own hash.
45 #
46 # Updating: change package.json/bun.lock, set the current system's
47 # hash below to lib.fakeHash, run `nix build`, copy the hash Nix
48 # reports on mismatch back in. Repeat per platform.
49 s3webuiNodeModules = pkgs.stdenv.mkDerivation {
50 pname = "smolsonic-s3webui-node-modules";
51 version = "0.10.0";
52
53 src = lib.fileset.toSource {
54 root = ./s3webui;
55 fileset = lib.fileset.unions [
56 ./s3webui/package.json
57 ./s3webui/bun.lock
58 ];
59 };
60
61 nativeBuildInputs = [ pkgs.bun ];
62
63 dontConfigure = true;
64
65 buildPhase = ''
66 runHook preBuild
67 export HOME=$(mktemp -d)
68 bun install --frozen-lockfile --no-progress
69 runHook postBuild
70 '';
71
72 installPhase = ''
73 runHook preInstall
74 mv node_modules $out
75 runHook postInstall
76 '';
77
78 dontFixup = true;
79
80 outputHashMode = "recursive";
81 outputHashAlgo = "sha256";
82 outputHash = {
83 x86_64-linux = "sha256-yKUGN1F8I1S6M5GgXeVA5y/u2gm8vYjyKxGLNO1nB90=";
84 aarch64-linux = lib.fakeHash;
85 aarch64-darwin = "sha256-VuAM4wkTAL8kIE0KseAC79F4+qLbLzR4g3AWiQjwaT0=";
86 x86_64-darwin = lib.fakeHash;
87 }.${system};
88 };
89
90 # Build the React SPA. The resulting `dist/` is embedded into the
91 # smolsonic binary at compile time via rust-embed.
92 s3webui = pkgs.stdenv.mkDerivation {
93 pname = "smolsonic-s3webui";
94 version = "0.10.0";
95
96 src = ./s3webui;
97
98 nativeBuildInputs = [ pkgs.bun pkgs.nodejs ];
99
100 configurePhase = ''
101 runHook preConfigure
102 cp -r ${s3webuiNodeModules} node_modules
103 chmod -R u+w node_modules
104 patchShebangs node_modules
105 export HOME=$(mktemp -d)
106 runHook postConfigure
107 '';
108
109 buildPhase = ''
110 runHook preBuild
111 bun run build
112 runHook postBuild
113 '';
114
115 installPhase = ''
116 runHook preInstall
117 cp -r dist $out
118 runHook postInstall
119 '';
120 };
121
122 # Common arguments can be set here to avoid repeating them later
123 commonArgs = {
124 inherit src;
125
126 pname = "smolsonic";
127 version = "0.10.0";
128
129 nativeBuildInputs = [
130 pkgs.pkg-config
131 ];
132
133 buildInputs = [
134 pkgs.openssl
135 pkgs.openssl.dev
136 ] ++ lib.optionals pkgs.stdenv.isDarwin [
137 pkgs.libiconv
138 ];
139
140 # rust-embed reads s3webui/dist at compile time. cleanCargoSource
141 # strips the s3webui directory, so we drop the pre-built SPA back
142 # in before cargo runs.
143 preBuild = ''
144 mkdir -p s3webui
145 cp -r ${s3webui} s3webui/dist
146 chmod -R u+w s3webui/dist
147 '';
148 };
149
150 craneLibLLvmTools = craneLib.overrideToolchain
151 (fenix.packages.${system}.complete.withComponents [
152 "cargo"
153 "llvm-tools"
154 "rustc"
155 ]);
156
157 # Build *just* the cargo dependencies, so we can reuse
158 # all of that work (e.g. via cachix) when running in CI
159 cargoArtifacts = craneLib.buildDepsOnly commonArgs;
160
161 # Build the actual crate itself, reusing the dependency
162 # artifacts from above.
163 smolsonic = craneLib.buildPackage (commonArgs // {
164 inherit cargoArtifacts;
165 });
166
167 in
168 {
169 checks = {
170 # Build the crate as part of `nix flake check` for convenience
171 inherit smolsonic;
172
173 # Run clippy (and deny all warnings) on the crate source,
174 # again, reusing the dependency artifacts from above.
175 smolsonic-clippy = craneLib.cargoClippy (commonArgs // {
176 inherit cargoArtifacts;
177 cargoClippyExtraArgs = "--all-targets -- --deny warnings";
178 });
179
180 smolsonic-doc = craneLib.cargoDoc (commonArgs // {
181 inherit cargoArtifacts;
182 });
183
184 # Check formatting
185 smolsonic-fmt = craneLib.cargoFmt {
186 inherit src;
187 };
188
189 # Audit dependencies
190 smolsonic-audit = craneLib.cargoAudit {
191 inherit src advisory-db;
192 };
193
194 # Run tests with cargo-nextest
195 smolsonic-nextest = craneLib.cargoNextest (commonArgs // {
196 inherit cargoArtifacts;
197 partitions = 1;
198 partitionType = "count";
199 });
200 } // lib.optionalAttrs (system == "x86_64-linux") {
201 # NB: cargo-tarpaulin only supports x86_64 systems
202 smolsonic-coverage = craneLib.cargoTarpaulin (commonArgs // {
203 inherit cargoArtifacts;
204 });
205 };
206
207 packages = {
208 default = smolsonic;
209 smolsonic-llvm-coverage = craneLibLLvmTools.cargoLlvmCov (commonArgs // {
210 inherit cargoArtifacts;
211 });
212 };
213
214 apps.default = flake-utils.lib.mkApp {
215 drv = smolsonic;
216 };
217
218 devShells.default = pkgs.mkShell {
219 inputsFrom = builtins.attrValues self.checks.${system};
220
221 nativeBuildInputs = with pkgs; [
222 cargo
223 rustc
224 ];
225 };
226 });
227}