diff options
Diffstat (limited to 'nixos/modules/services/misc/home-assistant.nix')
-rw-r--r-- | nixos/modules/services/misc/home-assistant.nix | 147 |
1 files changed, 136 insertions, 11 deletions
diff --git a/nixos/modules/services/misc/home-assistant.nix b/nixos/modules/services/misc/home-assistant.nix index 0477254e7c1..dcd825bba43 100644 --- a/nixos/modules/services/misc/home-assistant.nix +++ b/nixos/modules/services/misc/home-assistant.nix @@ -63,10 +63,12 @@ let }; in { - meta.maintainers = with maintainers; [ dotlambda ]; + meta.maintainers = teams.home-assistant.members; options.services.home-assistant = { - enable = mkEnableOption "Home Assistant"; + # Running home-assistant on NixOS is considered an installation method that is unsupported by the upstream project. + # https://github.com/home-assistant/architecture/blob/master/adr/0012-define-supported-installation-method.md#decision + enable = mkEnableOption "Home Assistant. Please note that this installation method is unsupported upstream"; configDir = mkOption { default = "/var/lib/hass"; @@ -183,8 +185,14 @@ in { }; package = mkOption { - default = pkgs.home-assistant; - defaultText = "pkgs.home-assistant"; + default = pkgs.home-assistant.overrideAttrs (oldAttrs: { + doInstallCheck = false; + }); + defaultText = literalExample '' + pkgs.home-assistant.overrideAttrs (oldAttrs: { + doInstallCheck = false; + }) + ''; type = types.package; example = literalExample '' pkgs.home-assistant.override { @@ -192,10 +200,11 @@ in { } ''; description = '' - Home Assistant package to use. + Home Assistant package to use. By default the tests are disabled, as they take a considerable amout of time to complete. Override <literal>extraPackages</literal> or <literal>extraComponents</literal> in order to add additional dependencies. If you specify <option>config</option> and do not set <option>autoExtraComponents</option> to <literal>false</literal>, overriding <literal>extraComponents</literal> will have no effect. + Avoid <literal>home-assistant.overridePythonAttrs</literal> if you use <literal>autoExtraComponents</literal>. ''; }; @@ -238,18 +247,135 @@ in { rm -f "${cfg.configDir}/ui-lovelace.yaml" ln -s ${lovelaceConfigFile} "${cfg.configDir}/ui-lovelace.yaml" ''); - serviceConfig = { - ExecStart = "${package}/bin/hass --config '${cfg.configDir}'"; + serviceConfig = let + # List of capabilities to equip home-assistant with, depending on configured components + capabilities = [ + # Empty string first, so we will never accidentally have an empty capability bounding set + # https://github.com/NixOS/nixpkgs/issues/120617#issuecomment-830685115 + "" + ] ++ (unique (optionals (useComponent "bluetooth_tracker" || useComponent "bluetooth_le_tracker") [ + # Required for interaction with hci devices and bluetooth sockets + # https://www.home-assistant.io/integrations/bluetooth_le_tracker/#rootless-setup-on-core-installs + "CAP_NET_ADMIN" + "CAP_NET_RAW" + ] ++ lib.optionals (useComponent "emulated_hue") [ + # Alexa looks for the service on port 80 + # https://www.home-assistant.io/integrations/emulated_hue + "CAP_NET_BIND_SERVICE" + ] ++ lib.optionals (useComponent "nmap_tracker") [ + # https://www.home-assistant.io/integrations/nmap_tracker#linux-capabilities + "CAP_NET_ADMIN" + "CAP_NET_BIND_SERVICE" + "CAP_NET_RAW" + ])); + componentsUsingBluetooth = [ + # Components that require the AF_BLUETOOTH address family + "bluetooth_tracker" + "bluetooth_le_tracker" + ]; + componentsUsingSerialDevices = [ + # Components that require access to serial devices (/dev/tty*) + # List generated from home-assistant documentation: + # git clone https://github.com/home-assistant/home-assistant.io/ + # cd source/_integrations + # rg "/dev/tty" -l | cut -d'/' -f3 | cut -d'.' -f1 | sort + # And then extended by references found in the source code, these + # mostly the ones using config flows already. + "acer_projector" + "alarmdecoder" + "arduino" + "blackbird" + "dsmr" + "edl21" + "elkm1" + "elv" + "enocean" + "firmata" + "flexit" + "gpsd" + "insteon" + "kwb" + "lacrosse" + "mhz19" + "modbus" + "modem_callerid" + "mysensors" + "nad" + "numato" + "rflink" + "rfxtrx" + "scsgate" + "serial" + "serial_pm" + "sms" + "upb" + "velbus" + "w800rf32" + "xbee" + "zha" + "zwave" + ]; + in { + ExecStart = "${package}/bin/hass --runner --config '${cfg.configDir}'"; ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; User = "hass"; Group = "hass"; Restart = "on-failure"; - ProtectSystem = "strict"; - ReadWritePaths = "${cfg.configDir}"; + RestartForceExitStatus = "100"; + SuccessExitStatus = "100"; KillSignal = "SIGINT"; + + # Hardening + AmbientCapabilities = capabilities; + CapabilityBoundingSet = capabilities; + DeviceAllow = (optionals (any useComponent componentsUsingSerialDevices) [ + "char-ttyACM rw" + "char-ttyAMA rw" + "char-ttyUSB rw" + ]); + DevicePolicy = "closed"; + LockPersonality = true; + MemoryDenyWriteExecute = true; + NoNewPrivileges = true; PrivateTmp = true; + PrivateUsers = false; # prevents gaining capabilities in the host namespace + ProtectClock = true; + ProtectControlGroups = true; + ProtectHome = true; + ProtectHostname = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectProc = "invisible"; + ProcSubset = "all"; + ProtectSystem = "strict"; RemoveIPC = true; - AmbientCapabilities = "cap_net_raw,cap_net_admin+eip"; + ReadWritePaths = let + # Allow rw access to explicitly configured paths + cfgPath = [ "config" "homeassistant" "allowlist_external_dirs" ]; + value = attrByPath cfgPath [] cfg; + allowPaths = if isList value then value else singleton value; + in [ "${cfg.configDir}" ] ++ allowPaths; + RestrictAddressFamilies = [ + "AF_INET" + "AF_INET6" + "AF_NETLINK" + "AF_UNIX" + ] ++ optionals (any useComponent componentsUsingBluetooth) [ + "AF_BLUETOOTH" + ]; + RestrictNamespaces = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + SupplementaryGroups = optionals (any useComponent componentsUsingSerialDevices) [ + "dialout" + ]; + SystemCallArchitectures = "native"; + SystemCallFilter = [ + "@system-service" + "~@privileged" + ]; + UMask = "0077"; }; path = [ "/run/wrappers" # needed for ping @@ -267,7 +393,6 @@ in { home = cfg.configDir; createHome = true; group = "hass"; - extraGroups = [ "dialout" ]; uid = config.ids.uids.hass; }; |