From 00b9d1f7546ba868a08651db276b2c2c5cd7f6c6 Mon Sep 17 00:00:00 2001 From: Martin Weinelt Date: Fri, 30 Aug 2024 01:46:36 +0200 Subject: [PATCH 1/7] nixos/home-assistant: replace global `with lib` --- .../home-automation/home-assistant.nix | 59 ++++++++++++++----- 1 file changed, 44 insertions(+), 15 deletions(-) diff --git a/nixos/modules/services/home-automation/home-assistant.nix b/nixos/modules/services/home-automation/home-assistant.nix index d5af0bcd5e91..bac538f42c99 100644 --- a/nixos/modules/services/home-automation/home-assistant.nix +++ b/nixos/modules/services/home-automation/home-assistant.nix @@ -1,8 +1,37 @@ { config, lib, pkgs, ... }: -with lib; - let + inherit (lib) + any + attrByPath + attrValues + concatMap + converge + elem + escapeShellArg + escapeShellArgs + filter + filterAttrsRecursive + hasAttrByPath + isAttrs + isDerivation + isList + literalExpression + mkEnableOption + mkIf + mkMerge + mkOption + mkRemovedOptionModule + mkRenamedOptionModule + optionals + optionalString + recursiveUpdate + singleton + splitString + types + unique + ; + cfg = config.services.home-assistant; format = pkgs.formats.yaml {}; @@ -17,7 +46,7 @@ let # Filter null values from the configuration, so that we can still advertise # optional options in the config attribute. - filteredConfig = lib.converge (lib.filterAttrsRecursive (_: v: ! elem v [ null ])) (lib.recursiveUpdate customLovelaceModulesResources (cfg.config or {})); + filteredConfig = converge (filterAttrsRecursive (_: v: ! elem v [ null ])) (recursiveUpdate customLovelaceModulesResources (cfg.config or {})); configFile = renderYAMLFile "configuration.yaml" filteredConfig; lovelaceConfigFile = renderYAMLFile "ui-lovelace.yaml" cfg.lovelaceConfig; @@ -42,7 +71,7 @@ let if isDerivation config then [ ] else if isAttrs config then - optional (config ? platform) config.platform + optionals (config ? platform) [ config.platform ] ++ concatMap usedPlatforms (attrValues config) else if isList config then concatMap usedPlatforms config @@ -67,7 +96,7 @@ let extraComponents = oldArgs.extraComponents or [] ++ extraComponents; extraPackages = ps: (oldArgs.extraPackages or (_: []) ps) ++ (cfg.extraPackages ps) - ++ (lib.concatMap (component: component.propagatedBuildInputs or []) cfg.customComponents); + ++ (concatMap (component: component.propagatedBuildInputs or []) cfg.customComponents); })); # Create a directory that holds all lovelace modules @@ -93,7 +122,7 @@ in { meta = { buildDocsInSandbox = false; - maintainers = teams.home-assistant.members; + maintainers = lib.teams.home-assistant.members; }; options.services.home-assistant = { @@ -471,12 +500,12 @@ in { networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [ cfg.config.http.server_port ]; # symlink the configuration to /etc/home-assistant - environment.etc = lib.mkMerge [ - (lib.mkIf (cfg.config != null && !cfg.configWritable) { + environment.etc = mkMerge [ + (mkIf (cfg.config != null && !cfg.configWritable) { "home-assistant/configuration.yaml".source = configFile; }) - (lib.mkIf (cfg.lovelaceConfig != null && !cfg.lovelaceConfigWritable) { + (mkIf (cfg.lovelaceConfig != null && !cfg.lovelaceConfigWritable) { "home-assistant/ui-lovelace.yaml".source = lovelaceConfigFile; }) ]; @@ -491,8 +520,8 @@ in { "mysql.service" "postgresql.service" ]; - reloadTriggers = lib.optional (cfg.config != null) configFile - ++ lib.optional (cfg.lovelaceConfig != null) lovelaceConfigFile; + reloadTriggers = optionals (cfg.config != null) [ configFile ] + ++ optionals (cfg.lovelaceConfig != null) [ lovelaceConfigFile ]; preStart = let copyConfig = if cfg.configWritable then '' @@ -541,20 +570,20 @@ in { environment.PYTHONPATH = package.pythonPath; serviceConfig = let # List of capabilities to equip home-assistant with, depending on configured components - capabilities = lib.unique ([ + capabilities = unique ([ # Empty string first, so we will never accidentally have an empty capability bounding set # https://github.com/NixOS/nixpkgs/issues/120617#issuecomment-830685115 "" - ] ++ lib.optionals (builtins.any useComponent componentsUsingBluetooth) [ + ] ++ optionals (any useComponent componentsUsingBluetooth) [ # Required for interaction with hci devices and bluetooth sockets, identified by bluetooth-adapters dependency # 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") [ + ] ++ 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") [ + ] ++ optionals (useComponent "nmap_tracker") [ # https://www.home-assistant.io/integrations/nmap_tracker#linux-capabilities "CAP_NET_ADMIN" "CAP_NET_BIND_SERVICE" From 179fd97dae9b5d99486e10384f54e458d1f14a0b Mon Sep 17 00:00:00 2001 From: Martin Weinelt Date: Fri, 30 Aug 2024 01:53:55 +0200 Subject: [PATCH 2/7] nixos/home-assistant: update component hardening lists --- .../services/home-automation/home-assistant.nix | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/nixos/modules/services/home-automation/home-assistant.nix b/nixos/modules/services/home-automation/home-assistant.nix index bac538f42c99..963d0ba56c7b 100644 --- a/nixos/modules/services/home-automation/home-assistant.nix +++ b/nixos/modules/services/home-automation/home-assistant.nix @@ -611,12 +611,14 @@ in { "inkbird" "improv_ble" "keymitt_ble" - "leaone-ble" + "ld2410_ble" + "leaone" "led_ble" "medcom_ble" "melnor" "moat" "mopeka" + "motionblinds_ble" "oralb" "private_ble_device" "qingping" @@ -650,25 +652,34 @@ in { # mostly the ones using config flows already. "acer_projector" "alarmdecoder" + "aurora_abb_powerone" "blackbird" + "bryant_evolution" + "crownstone" "deconz" "dsmr" "edl21" "elkm1" "elv" "enocean" + "homeassistant_hardware" + "homeassistant_yellow" "firmata" "flexit" "gpsd" "insteon" "kwb" "lacrosse" + "landisgyr_heat_meter" "modbus" "modem_callerid" "mysensors" "nad" "numato" + "nut" + "opentherm_gw" "otbr" + "rainforst_raven" "rflink" "rfxtrx" "scsgate" From 28b8aa8c4c9862b856164de589e4ca294fe2b194 Mon Sep 17 00:00:00 2001 From: Martin Weinelt Date: Fri, 30 Aug 2024 03:23:55 +0200 Subject: [PATCH 3/7] nixos/home-assistant: support extraArgs --- .../home-automation/home-assistant.nix | 25 ++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/nixos/modules/services/home-automation/home-assistant.nix b/nixos/modules/services/home-automation/home-assistant.nix index 963d0ba56c7b..2b9113eabdc5 100644 --- a/nixos/modules/services/home-automation/home-assistant.nix +++ b/nixos/modules/services/home-automation/home-assistant.nix @@ -1,4 +1,4 @@ -{ config, lib, pkgs, ... }: +{ config, lib, pkgs, utils, ... }: let inherit (lib) @@ -32,6 +32,10 @@ let unique ; + inherit (utils) + escapeSystemdExecArgs + ; + cfg = config.services.home-assistant; format = pkgs.formats.yaml {}; @@ -130,6 +134,15 @@ in { # 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"; + extraArgs = mkOption { + type = types.listOf types.str; + default = [ ]; + example = [ "--debug" ]; + description = '' + Extra arguments to pass to the hass executable. + ''; + }; + configDir = mkOption { default = "/var/lib/hass"; type = types.path; @@ -695,8 +708,14 @@ in { "zwave_js" ]; in { - ExecStart = "${package}/bin/hass --config '${cfg.configDir}'"; - ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; + ExecStart = escapeSystemdExecArgs ([ + (lib.getExe package) + "--config" cfg.configDir + ] ++ cfg.extraArgs); + ExecReload = (escapeSystemdExecArgs [ + (lib.getExe' pkgs.coreutils "kill") + "-HUP" + ]) + " $MAINPID"; User = "hass"; Group = "hass"; WorkingDirectory = cfg.configDir; From ee59cca0f10442ec9fdc5fb7b1cec0fdf4b5cfb6 Mon Sep 17 00:00:00 2001 From: Martin Weinelt Date: Fri, 30 Aug 2024 03:47:03 +0200 Subject: [PATCH 4/7] nixos/home-assistant: add reference to signal handling section --- nixos/modules/services/home-automation/home-assistant.nix | 3 +++ 1 file changed, 3 insertions(+) diff --git a/nixos/modules/services/home-automation/home-assistant.nix b/nixos/modules/services/home-automation/home-assistant.nix index 2b9113eabdc5..599d33724ad3 100644 --- a/nixos/modules/services/home-automation/home-assistant.nix +++ b/nixos/modules/services/home-automation/home-assistant.nix @@ -720,6 +720,9 @@ in { Group = "hass"; WorkingDirectory = cfg.configDir; Restart = "on-failure"; + + # Signal handling + # homeassistant/helpers/signal.py RestartForceExitStatus = "100"; SuccessExitStatus = "100"; KillSignal = "SIGINT"; From be150023f7c379c61bdef5f8bbfbd48c12ee90ad Mon Sep 17 00:00:00 2001 From: Martin Weinelt Date: Fri, 30 Aug 2024 03:39:38 +0200 Subject: [PATCH 5/7] nixos/tests/home-assistant: fix reload expectations When sending SIGHUP to hass it will exit with code 100, which is the codified exit code to trigger a restart. This is useful, because it can allow triggering a restart from within the frontend. It was previously assumed that it would result in a reload, which would keep the same interpreter process intact. That is not the case and so the assumption that the PID would stay the same was flawed and only succeeded due to race conditions. --- nixos/tests/home-assistant.nix | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/nixos/tests/home-assistant.nix b/nixos/tests/home-assistant.nix index 47902fa4e134..5ff142b08d7a 100644 --- a/nixos/tests/home-assistant.nix +++ b/nixos/tests/home-assistant.nix @@ -206,21 +206,21 @@ in { with subtest("Check extra components are considered in systemd unit hardening"): hass.succeed("systemctl show -p DeviceAllow home-assistant.service | grep -q char-ttyUSB") - with subtest("Check service reloads when configuration changes"): + with subtest("Check service restart from SIGHUP"): pid = hass.succeed("systemctl show --property=MainPID home-assistant.service") cursor = get_journal_cursor() hass.succeed("${system}/specialisation/differentName/bin/switch-to-configuration test") - new_pid = hass.succeed("systemctl show --property=MainPID home-assistant.service") - assert pid == new_pid, "The PID of the process should not change between process reloads" wait_for_homeassistant(cursor) + new_pid = hass.succeed("systemctl show --property=MainPID home-assistant.service") + assert pid != new_pid, "The PID of the process must change after sending SIGHUP" with subtest("Check service restarts when dependencies change"): pid = new_pid cursor = get_journal_cursor() hass.succeed("${system}/specialisation/newFeature/bin/switch-to-configuration test") - new_pid = hass.succeed("systemctl show --property=MainPID home-assistant.service") - assert pid != new_pid, "The PID of the process should change when its PYTHONPATH changess" wait_for_homeassistant(cursor) + new_pid = hass.succeed("systemctl show --property=MainPID home-assistant.service") + assert pid != new_pid, "The PID of the process must change when its PYTHONPATH changess" with subtest("Check that new components get setup after restart"): journal = get_journal_since(cursor) From 579ade1ed8370171f0e1bc482d0435fa5f1eb950 Mon Sep 17 00:00:00 2001 From: Martin Weinelt Date: Mon, 2 Sep 2024 02:11:22 +0200 Subject: [PATCH 6/7] nixos/tests/home-assistant: fix testing of restart with new dependency The backup module is part of the default integrations and as such it will always be loaded. Replace it with the prometheus module, for which this is probably unlikely to ever become the case. --- nixos/tests/home-assistant.nix | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nixos/tests/home-assistant.nix b/nixos/tests/home-assistant.nix index 5ff142b08d7a..5235d41f0297 100644 --- a/nixos/tests/home-assistant.nix +++ b/nixos/tests/home-assistant.nix @@ -122,7 +122,7 @@ in { # Cause a configuration change that requires a service restart as we added a new runtime dependency specialisation.newFeature = { inheritParentConfig = true; - configuration.services.home-assistant.config.backup = {}; + configuration.services.home-assistant.config.prometheus = {}; }; specialisation.removeCustomThings = { @@ -224,7 +224,7 @@ in { with subtest("Check that new components get setup after restart"): journal = get_journal_since(cursor) - for domain in ["backup"]: + for domain in ["prometheus"]: assert f"Setup of domain {domain} took" in journal, f"{domain} setup missing" with subtest("Check custom components and custom lovelace modules get removed"): From 2b03b3739303240c1cb3680cad829b4f3e197121 Mon Sep 17 00:00:00 2001 From: Martin Weinelt Date: Mon, 2 Sep 2024 01:54:06 +0200 Subject: [PATCH 7/7] nixos/tests/home-assistant: call with runTest --- nixos/tests/all-tests.nix | 2 +- nixos/tests/home-assistant.nix | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index ab50acd739b9..927382091e72 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -456,7 +456,7 @@ in { hitch = handleTest ./hitch {}; hledger-web = handleTest ./hledger-web.nix {}; hockeypuck = handleTest ./hockeypuck.nix { }; - home-assistant = handleTest ./home-assistant.nix {}; + home-assistant = runTest ./home-assistant.nix; hostname = handleTest ./hostname.nix {}; hound = handleTest ./hound.nix {}; hub = handleTest ./git/hub.nix {}; diff --git a/nixos/tests/home-assistant.nix b/nixos/tests/home-assistant.nix index 5235d41f0297..7b4e67192f29 100644 --- a/nixos/tests/home-assistant.nix +++ b/nixos/tests/home-assistant.nix @@ -1,4 +1,7 @@ -import ./make-test-python.nix ({ pkgs, lib, ... }: +{ + lib, + ... +}: let configDir = "/var/lib/foobar"; @@ -242,4 +245,4 @@ in { hass.log(hass.succeed("systemctl cat home-assistant.service")) hass.log(hass.succeed("systemd-analyze security home-assistant.service")) ''; -}) +}