1{
2 config,
3 lib,
4 pkgs,
5 utils,
6 ...
7}:
8let
9 cfg = config.hardware.bluetooth;
10 package = cfg.package;
11
12 inherit (lib)
13 mkEnableOption
14 mkIf
15 mkOption
16 mkPackageOption
17 mkRenamedOptionModule
18 mkRemovedOptionModule
19 concatStringsSep
20 optional
21 optionalAttrs
22 recursiveUpdate
23 types
24 ;
25
26 cfgFmt = pkgs.formats.ini { };
27
28 defaults = {
29 General.ControllerMode = "dual";
30 Policy.AutoEnable = cfg.powerOnBoot;
31 };
32
33 hasDisabledPlugins = builtins.length cfg.disabledPlugins > 0;
34
35in
36{
37 imports = [
38 (mkRenamedOptionModule [ "hardware" "bluetooth" "config" ] [ "hardware" "bluetooth" "settings" ])
39 (mkRemovedOptionModule [ "hardware" "bluetooth" "extraConfig" ] ''
40 Use hardware.bluetooth.settings instead.
41
42 This is part of the general move to use structured settings instead of raw
43 text for config as introduced by RFC0042:
44 https://github.com/NixOS/rfcs/blob/master/rfcs/0042-config-option.md
45 '')
46 ];
47
48 ###### interface
49
50 options = {
51
52 hardware.bluetooth = {
53 enable = mkEnableOption "support for Bluetooth";
54
55 hsphfpd.enable = mkEnableOption "support for hsphfpd[-prototype] implementation";
56
57 powerOnBoot = mkOption {
58 type = types.bool;
59 default = true;
60 description = "Whether to power up the default Bluetooth controller on boot.";
61 };
62
63 package = mkPackageOption pkgs "bluez" { };
64
65 disabledPlugins = mkOption {
66 type = types.listOf types.str;
67 default = [ ];
68 description = "Built-in plugins to disable";
69 };
70
71 settings = mkOption {
72 type = cfgFmt.type;
73 default = { };
74 example = {
75 General = {
76 ControllerMode = "bredr";
77 };
78 };
79 description = ''
80 Set configuration for system-wide bluetooth (/etc/bluetooth/main.conf).
81 See <https://github.com/bluez/bluez/blob/master/src/main.conf> for full list of options.
82 '';
83 };
84
85 input = mkOption {
86 type = cfgFmt.type;
87 default = { };
88 example = {
89 General = {
90 IdleTimeout = 30;
91 ClassicBondedOnly = true;
92 };
93 };
94 description = ''
95 Set configuration for the input service (/etc/bluetooth/input.conf).
96 See <https://github.com/bluez/bluez/blob/master/profiles/input/input.conf> for full list of options.
97 '';
98 };
99
100 network = mkOption {
101 type = cfgFmt.type;
102 default = { };
103 example = {
104 General = {
105 DisableSecurity = true;
106 };
107 };
108 description = ''
109 Set configuration for the network service (/etc/bluetooth/network.conf).
110 See <https://github.com/bluez/bluez/blob/master/profiles/network/network.conf> for full list of options.
111 '';
112 };
113 };
114 };
115
116 ###### implementation
117
118 config = mkIf cfg.enable {
119 environment.systemPackages = [ package ] ++ optional cfg.hsphfpd.enable pkgs.hsphfpd;
120
121 environment.etc."bluetooth/input.conf".source = cfgFmt.generate "input.conf" cfg.input;
122 environment.etc."bluetooth/network.conf".source = cfgFmt.generate "network.conf" cfg.network;
123 environment.etc."bluetooth/main.conf".source = cfgFmt.generate "main.conf" (
124 recursiveUpdate defaults cfg.settings
125 );
126 services.udev.packages = [ package ];
127 services.dbus.packages = [ package ] ++ optional cfg.hsphfpd.enable pkgs.hsphfpd;
128 systemd.packages = [ package ];
129
130 systemd.services =
131 {
132 bluetooth =
133 let
134 # `man bluetoothd` will refer to main.conf in the nix store but bluez
135 # will in fact load the configuration file at /etc/bluetooth/main.conf
136 # so force it here to avoid any ambiguity and things suddenly breaking
137 # if/when the bluez derivation is changed.
138 args = [
139 "-f"
140 "/etc/bluetooth/main.conf"
141 ] ++ optional hasDisabledPlugins "--noplugin=${concatStringsSep "," cfg.disabledPlugins}";
142 in
143 {
144 wantedBy = [ "bluetooth.target" ];
145 aliases = [ "dbus-org.bluez.service" ];
146 # restarting can leave people without a mouse/keyboard
147 restartIfChanged = false;
148 serviceConfig = {
149 ExecStart = [
150 ""
151 "${package}/libexec/bluetooth/bluetoothd ${utils.escapeSystemdExecArgs args}"
152 ];
153 CapabilityBoundingSet = [
154 "CAP_NET_BIND_SERVICE" # sockets and tethering
155 ];
156 ConfigurationDirectoryMode = "0755";
157 NoNewPrivileges = true;
158 RestrictNamespaces = true;
159 ProtectControlGroups = true;
160 MemoryDenyWriteExecute = true;
161 RestrictSUIDSGID = true;
162 SystemCallArchitectures = "native";
163 SystemCallFilter = "@system-service";
164 LockPersonality = true;
165 RestrictRealtime = true;
166 ProtectProc = "invisible";
167 PrivateTmp = true;
168
169 PrivateUsers = false;
170
171 # loading hardware modules
172 ProtectKernelModules = false;
173 ProtectKernelTunables = false;
174
175 PrivateNetwork = false; # tethering
176 };
177 };
178 }
179 // (optionalAttrs cfg.hsphfpd.enable {
180 hsphfpd = {
181 after = [ "bluetooth.service" ];
182 requires = [ "bluetooth.service" ];
183 wantedBy = [ "bluetooth.target" ];
184
185 description = "A prototype implementation used for connecting HSP/HFP Bluetooth devices";
186 serviceConfig.ExecStart = "${pkgs.hsphfpd}/bin/hsphfpd.pl";
187 };
188 });
189
190 systemd.user.services =
191 {
192 obex.aliases = [ "dbus-org.bluez.obex.service" ];
193 }
194 // optionalAttrs cfg.hsphfpd.enable {
195 telephony_client = {
196 wantedBy = [ "default.target" ];
197
198 description = "telephony_client for hsphfpd";
199 serviceConfig.ExecStart = "${pkgs.hsphfpd}/bin/telephony_client.pl";
200 };
201 };
202 };
203}