a Jellyfin & Subsonic client for the terminal — powered by mpv, Chromecast and UPnP MediaRenderer
mpv
chromecast
mpris
navidrome
jellyfin
upnp
tui
5.7 kB
183 lines
1{
2 description = "fin - a neon-electric Jellyfin TUI client for mpv & Chromecast";
3
4 inputs = {
5 nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
6
7 # Current crane doesn't expose a `nixpkgs` input, so we don't follow it.
8 crane.url = "github:ipetkov/crane";
9
10 fenix = {
11 url = "github:nix-community/fenix";
12 inputs.nixpkgs.follows = "nixpkgs";
13 inputs.rust-analyzer-src.follows = "";
14 };
15
16 flake-utils.url = "github:numtide/flake-utils";
17
18 advisory-db = {
19 url = "github:rustsec/advisory-db";
20 flake = false;
21 };
22 };
23
24 outputs = { self, nixpkgs, crane, fenix, flake-utils, advisory-db, ... }:
25 flake-utils.lib.eachDefaultSystem (system:
26 let
27 pkgs = import nixpkgs {
28 inherit system;
29 };
30
31 inherit (pkgs) lib;
32
33 craneLib = crane.mkLib pkgs;
34
35 src = craneLib.cleanCargoSource ./.;
36
37 # fin uses rustls end-to-end, so no openssl.
38 # mpv is a *runtime* requirement (we spawn it for local playback and
39 # the CLI does a preflight `mpv --version` at startup). We fold it
40 # into PATH via a makeWrapper post-fixup below.
41 commonArgs = {
42 inherit src;
43
44 pname = "fin";
45 version = "0.3.1";
46 strictDeps = true;
47
48 # No native TLS or system libs needed — pure Rust deps.
49 # Modern nixpkgs (post-25.05) auto-links the Darwin SDK, so no
50 # framework references here — `darwin.apple_sdk_11_0` was removed
51 # as a legacy compatibility stub.
52 nativeBuildInputs = [
53 pkgs.pkg-config
54 ] ++ lib.optionals pkgs.stdenv.isDarwin [
55 # coreaudio-sys generates its CoreAudio bindings with bindgen at
56 # build time; bindgenHook provides libclang (LIBCLANG_PATH) and
57 # points clang at the Nix Apple SDK headers.
58 pkgs.rustPlatform.bindgenHook
59 ];
60
61 buildInputs = lib.optionals pkgs.stdenv.isDarwin [
62 pkgs.libiconv
63 ] ++ lib.optionals pkgs.stdenv.isLinux [
64 # cpal links against ALSA on Linux for the local audio output path.
65 pkgs.alsa-lib
66 ];
67
68 # Workspace has one bin target — build just that.
69 cargoExtraArgs = "--locked --bin fin";
70 };
71
72 craneLibLLvmTools = craneLib.overrideToolchain
73 (fenix.packages.${system}.complete.withComponents [
74 "cargo"
75 "llvm-tools"
76 "rustc"
77 ]);
78
79 # Cache the dependency graph separately from the crate source.
80 cargoArtifacts = craneLib.buildDepsOnly commonArgs;
81
82 finUnwrapped = craneLib.buildPackage (commonArgs // {
83 inherit cargoArtifacts;
84 doCheck = false;
85 });
86
87 # Wrap the binary so `mpv` is always discoverable at runtime, even
88 # when installed via `nix profile install`.
89 fin = pkgs.symlinkJoin {
90 name = "fin-${finUnwrapped.version}";
91 paths = [ finUnwrapped ];
92 nativeBuildInputs = [ pkgs.makeWrapper ];
93 postBuild = ''
94 wrapProgram $out/bin/fin \
95 --prefix PATH : ${lib.makeBinPath [ pkgs.mpv ]}
96 '';
97 meta = {
98 description = "A neon-electric Jellyfin TUI client for mpv & Chromecast";
99 homepage = "https://github.com/tsirysndr/fin";
100 license = lib.licenses.mpl20;
101 mainProgram = "fin";
102 platforms = lib.platforms.unix;
103 };
104 };
105
106 in
107 {
108 checks = {
109 inherit fin;
110
111 fin-clippy = craneLib.cargoClippy (commonArgs // {
112 inherit cargoArtifacts;
113 cargoClippyExtraArgs = "--all-targets -- --deny warnings";
114 });
115
116 fin-doc = craneLib.cargoDoc (commonArgs // {
117 inherit cargoArtifacts;
118 });
119
120 fin-fmt = craneLib.cargoFmt {
121 inherit src;
122 };
123
124 fin-audit = craneLib.cargoAudit {
125 inherit src advisory-db;
126 };
127
128 fin-nextest = craneLib.cargoNextest (commonArgs // {
129 inherit cargoArtifacts;
130 partitions = 1;
131 partitionType = "count";
132 });
133 } // lib.optionalAttrs (system == "x86_64-linux") {
134 fin-coverage = craneLib.cargoTarpaulin (commonArgs // {
135 inherit cargoArtifacts;
136 });
137 };
138
139 packages = {
140 default = fin;
141 fin = fin;
142 fin-unwrapped = finUnwrapped;
143
144 fin-llvm-coverage = craneLibLLvmTools.cargoLlvmCov (commonArgs // {
145 inherit cargoArtifacts;
146 });
147 };
148
149 apps.default = flake-utils.lib.mkApp {
150 drv = fin;
151 name = "fin";
152 };
153
154 devShells.default = pkgs.mkShell {
155 inputsFrom = builtins.attrValues self.checks.${system};
156
157 # Build-time tools. pkg-config is required so cpal's build.rs can
158 # resolve libasound on Linux.
159 nativeBuildInputs = with pkgs; [
160 cargo
161 rustc
162 rustfmt
163 clippy
164 rust-analyzer
165 mpv
166 pkg-config
167 ];
168
169 # Link-time libraries. Position matters: pkg-config only picks up
170 # `.pc` files from `buildInputs`, so alsa-lib MUST live here (not
171 # in nativeBuildInputs) for the cpal → ALSA link to resolve.
172 buildInputs = with pkgs; lib.optionals stdenv.isDarwin [
173 libiconv
174 ] ++ lib.optionals stdenv.isLinux [
175 alsa-lib
176 ];
177
178 shellHook = ''
179 echo "⚡ fin dev shell — mpv $(mpv --version | head -n1 | cut -d' ' -f2) ready"
180 '';
181 };
182 });
183}