1{
2 lib,
3 stdenv,
4 rustPlatform,
5 fetchFromGitHub,
6 pkg-config,
7 protobuf,
8 makeWrapper,
9 git,
10 dbus,
11 libnftnl,
12 libmnl,
13 libwg,
14 darwin,
15 enableOpenvpn ? true,
16 openvpn-mullvad,
17 shadowsocks-rust,
18 installShellFiles,
19 writeShellScriptBin,
20 versionCheckHook,
21}:
22let
23 # NOTE(cole-h): This is necessary because wireguard-go-rs executes go in its build.rs (whose goal
24 # is to produce $OUT_DIR/libwg.a), and a mixed Rust-Go build is non-trivial (read: I didn't want
25 # to attempt it). So, we just fake the "go" binary and do what it would have done: put libwg.a
26 # under $OUT_DIR so that it can be linked against.
27 fakeGoCopyLibwg = writeShellScriptBin "go" ''
28 [ ! -e "$OUT_DIR"/libwg.a ] && cp ${libwg}/lib/libwg.a "$OUT_DIR"/libwg.a
29 '';
30in
31rustPlatform.buildRustPackage rec {
32 pname = "mullvad";
33 version = "2025.7";
34
35 src = fetchFromGitHub {
36 owner = "mullvad";
37 repo = "mullvadvpn-app";
38 tag = version;
39 fetchSubmodules = true;
40 hash = "sha256-q5RYgU7VlhTXAN0uQeHNTJ1eFCQZzymLo/eiKr805O8=";
41 };
42
43 cargoHash = "sha256-UUYAmNdzTthoWOIU5wr7Q059MAezpuRvCadGTjwoKGM=";
44
45 cargoBuildFlags = [
46 "-p mullvad-daemon --bin mullvad-daemon"
47 "-p mullvad-cli --bin mullvad"
48 "-p mullvad-setup --bin mullvad-setup"
49 "-p mullvad-problem-report --bin mullvad-problem-report"
50 "-p mullvad-exclude --bin mullvad-exclude"
51 "-p tunnel-obfuscation --bin tunnel-obfuscation"
52 "-p talpid-openvpn-plugin --lib"
53 ];
54
55 checkFlags = [
56 "--skip=version_check"
57 "--skip=config_resolver::test"
58 ];
59
60 nativeBuildInputs = [
61 pkg-config
62 protobuf
63 makeWrapper
64 git
65 installShellFiles
66 fakeGoCopyLibwg
67 ];
68
69 buildInputs =
70 lib.optionals stdenv.hostPlatform.isLinux [
71 dbus.dev
72 libnftnl
73 libmnl
74 ]
75 ++ lib.optionals stdenv.hostPlatform.isDarwin [
76 darwin.libpcap
77 ];
78
79 postInstall = ''
80 compdir=$(mktemp -d)
81 for shell in bash zsh fish; do
82 $out/bin/mullvad shell-completions $shell $compdir
83 done
84 installShellCompletion --cmd mullvad \
85 --bash $compdir/mullvad.bash \
86 --zsh $compdir/_mullvad \
87 --fish $compdir/mullvad.fish
88 '';
89
90 postFixup =
91 # Files necessary for OpenVPN tunnels to work.
92 lib.optionalString enableOpenvpn ''
93 mkdir -p $out/share/mullvad
94 cp dist-assets/ca.crt $out/share/mullvad
95 ln -s ${openvpn-mullvad}/bin/openvpn $out/share/mullvad
96 ln -s ${shadowsocks-rust}/bin/sslocal $out/share/mullvad
97 ''
98 +
99 # Set the directory where Mullvad will look for its resources by default to
100 # `$out/share`, so that we can avoid putting the files in `$out/bin` --
101 # Mullvad defaults to looking inside the directory its binary is located in
102 # for its resources.
103 ''
104 wrapProgram $out/bin/mullvad-daemon \
105 --set-default MULLVAD_RESOURCE_DIR "$out/share/mullvad"
106 '';
107
108 __darwinAllowLocalNetworking = true;
109
110 nativeInstallCheckInputs = [
111 versionCheckHook
112 ];
113 versionCheckProgramArg = "--version";
114 doInstallCheck = true;
115
116 passthru = {
117 inherit libwg;
118 inherit openvpn-mullvad;
119 };
120
121 meta = {
122 description = "Mullvad VPN command-line client tools";
123 homepage = "https://github.com/mullvad/mullvadvpn-app";
124 changelog = "https://github.com/mullvad/mullvadvpn-app/blob/${version}/CHANGELOG.md";
125 license = lib.licenses.gpl3Only;
126 maintainers = with lib.maintainers; [ cole-h ];
127 mainProgram = "mullvad";
128 };
129}