{ config, lib, pkgs, utils, ... }: let cfg = config.hardware.bluetooth; package = cfg.package; inherit (lib) mkEnableOption mkIf mkOption mkPackageOption mkRenamedOptionModule mkRemovedOptionModule concatStringsSep optional optionalAttrs recursiveUpdate types ; cfgFmt = pkgs.formats.ini { }; defaults = { General.ControllerMode = "dual"; Policy.AutoEnable = cfg.powerOnBoot; }; hasDisabledPlugins = builtins.length cfg.disabledPlugins > 0; in { imports = [ (mkRenamedOptionModule [ "hardware" "bluetooth" "config" ] [ "hardware" "bluetooth" "settings" ]) (mkRemovedOptionModule [ "hardware" "bluetooth" "extraConfig" ] '' Use hardware.bluetooth.settings instead. This is part of the general move to use structured settings instead of raw text for config as introduced by RFC0042: https://github.com/NixOS/rfcs/blob/master/rfcs/0042-config-option.md '') ]; ###### interface options = { hardware.bluetooth = { enable = mkEnableOption "support for Bluetooth"; hsphfpd.enable = mkEnableOption "support for hsphfpd[-prototype] implementation"; powerOnBoot = mkOption { type = types.bool; default = true; description = "Whether to power up the default Bluetooth controller on boot."; }; package = mkPackageOption pkgs "bluez" { }; disabledPlugins = mkOption { type = types.listOf types.str; default = [ ]; description = "Built-in plugins to disable"; }; settings = mkOption { type = cfgFmt.type; default = { }; example = { General = { ControllerMode = "bredr"; }; }; description = '' Set configuration for system-wide bluetooth (/etc/bluetooth/main.conf). See for full list of options. ''; }; input = mkOption { type = cfgFmt.type; default = { }; example = { General = { IdleTimeout = 30; ClassicBondedOnly = true; }; }; description = '' Set configuration for the input service (/etc/bluetooth/input.conf). See for full list of options. ''; }; network = mkOption { type = cfgFmt.type; default = { }; example = { General = { DisableSecurity = true; }; }; description = '' Set configuration for the network service (/etc/bluetooth/network.conf). See for full list of options. ''; }; }; }; ###### implementation config = mkIf cfg.enable { environment.systemPackages = [ package ] ++ optional cfg.hsphfpd.enable pkgs.hsphfpd; environment.etc."bluetooth/input.conf".source = cfgFmt.generate "input.conf" cfg.input; environment.etc."bluetooth/network.conf".source = cfgFmt.generate "network.conf" cfg.network; environment.etc."bluetooth/main.conf".source = cfgFmt.generate "main.conf" ( recursiveUpdate defaults cfg.settings ); services.udev.packages = [ package ]; services.dbus.packages = [ package ] ++ optional cfg.hsphfpd.enable pkgs.hsphfpd; systemd.packages = [ package ]; systemd.services = { bluetooth = let # `man bluetoothd` will refer to main.conf in the nix store but bluez # will in fact load the configuration file at /etc/bluetooth/main.conf # so force it here to avoid any ambiguity and things suddenly breaking # if/when the bluez derivation is changed. args = [ "-f" "/etc/bluetooth/main.conf" ] ++ optional hasDisabledPlugins "--noplugin=${concatStringsSep "," cfg.disabledPlugins}"; in { wantedBy = [ "bluetooth.target" ]; aliases = [ "dbus-org.bluez.service" ]; # restarting can leave people without a mouse/keyboard restartIfChanged = false; serviceConfig = { ExecStart = [ "" "${package}/libexec/bluetooth/bluetoothd ${utils.escapeSystemdExecArgs args}" ]; CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" # sockets and tethering ]; ConfigurationDirectoryMode = "0755"; NoNewPrivileges = true; RestrictNamespaces = true; ProtectControlGroups = true; MemoryDenyWriteExecute = true; RestrictSUIDSGID = true; SystemCallArchitectures = "native"; SystemCallFilter = "@system-service"; LockPersonality = true; RestrictRealtime = true; ProtectProc = "invisible"; PrivateTmp = true; PrivateUsers = false; # loading hardware modules ProtectKernelModules = false; ProtectKernelTunables = false; PrivateNetwork = false; # tethering }; }; } // (optionalAttrs cfg.hsphfpd.enable { hsphfpd = { after = [ "bluetooth.service" ]; requires = [ "bluetooth.service" ]; wantedBy = [ "bluetooth.target" ]; description = "A prototype implementation used for connecting HSP/HFP Bluetooth devices"; serviceConfig.ExecStart = "${pkgs.hsphfpd}/bin/hsphfpd.pl"; }; }); systemd.user.services = { obex.aliases = [ "dbus-org.bluez.obex.service" ]; } // optionalAttrs cfg.hsphfpd.enable { telephony_client = { wantedBy = [ "default.target" ]; description = "telephony_client for hsphfpd"; serviceConfig.ExecStart = "${pkgs.hsphfpd}/bin/telephony_client.pl"; }; }; }; }