at 24.11-pre 9.0 kB view raw
1import ./make-test-python.nix ({ pkgs, lib, ... }: 2 3let 4 configDir = "/var/lib/foobar"; 5in { 6 name = "home-assistant"; 7 meta.maintainers = lib.teams.home-assistant.members; 8 9 nodes.hass = { pkgs, ... }: { 10 services.postgresql = { 11 enable = true; 12 ensureDatabases = [ "hass" ]; 13 ensureUsers = [{ 14 name = "hass"; 15 ensureDBOwnership = true; 16 }]; 17 }; 18 19 services.home-assistant = { 20 enable = true; 21 inherit configDir; 22 23 # provide dependencies through package overrides 24 package = (pkgs.home-assistant.override { 25 extraPackages = ps: with ps; [ 26 colorama 27 ]; 28 extraComponents = [ 29 # test char-tty device allow propagation into the service 30 "zha" 31 ]; 32 }); 33 34 # provide component dependencies explicitly from the module 35 extraComponents = [ 36 "mqtt" 37 ]; 38 39 # provide package for postgresql support 40 extraPackages = python3Packages: with python3Packages; [ 41 psycopg2 42 ]; 43 44 # test loading custom components 45 customComponents = with pkgs.home-assistant-custom-components; [ 46 prometheus_sensor 47 ]; 48 49 # test loading lovelace modules 50 customLovelaceModules = with pkgs.home-assistant-custom-lovelace-modules; [ 51 mini-graph-card 52 ]; 53 54 config = { 55 homeassistant = { 56 name = "Home"; 57 time_zone = "UTC"; 58 latitude = "0.0"; 59 longitude = "0.0"; 60 elevation = 0; 61 }; 62 63 # configure the recorder component to use the postgresql db 64 recorder.db_url = "postgresql://@/hass"; 65 66 # we can't load default_config, because the updater requires 67 # network access and would cause an error, so load frontend 68 # here explicitly. 69 # https://www.home-assistant.io/integrations/frontend/ 70 frontend = {}; 71 72 # include some popular integrations, that absolutely shouldn't break 73 knx = {}; 74 shelly = {}; 75 zha = {}; 76 77 # set up a wake-on-lan switch to test capset capability required 78 # for the ping suid wrapper 79 # https://www.home-assistant.io/integrations/wake_on_lan/ 80 switch = [ { 81 platform = "wake_on_lan"; 82 mac = "00:11:22:33:44:55"; 83 host = "127.0.0.1"; 84 } ]; 85 86 # test component-based capability assignment (CAP_NET_BIND_SERVICE) 87 # https://www.home-assistant.io/integrations/emulated_hue/ 88 emulated_hue = { 89 host_ip = "127.0.0.1"; 90 listen_port = 80; 91 }; 92 93 # https://www.home-assistant.io/integrations/logger/ 94 logger = { 95 default = "info"; 96 }; 97 }; 98 99 # configure the sample lovelace dashboard 100 lovelaceConfig = { 101 title = "My Awesome Home"; 102 views = [{ 103 title = "Example"; 104 cards = [{ 105 type = "markdown"; 106 title = "Lovelace"; 107 content = "Welcome to your **Lovelace UI**."; 108 }]; 109 }]; 110 }; 111 lovelaceConfigWritable = true; 112 }; 113 114 # Cause a configuration change inside `configuration.yml` and verify that the process is being reloaded. 115 specialisation.differentName = { 116 inheritParentConfig = true; 117 configuration.services.home-assistant.config.homeassistant.name = lib.mkForce "Test Home"; 118 }; 119 120 # Cause a configuration change that requires a service restart as we added a new runtime dependency 121 specialisation.newFeature = { 122 inheritParentConfig = true; 123 configuration.services.home-assistant.config.backup = {}; 124 }; 125 126 specialisation.removeCustomThings = { 127 inheritParentConfig = true; 128 configuration.services.home-assistant = { 129 customComponents = lib.mkForce []; 130 customLovelaceModules = lib.mkForce []; 131 }; 132 }; 133 }; 134 135 testScript = { nodes, ... }: let 136 system = nodes.hass.system.build.toplevel; 137 in 138 '' 139 import json 140 141 start_all() 142 143 144 def get_journal_cursor() -> str: 145 exit, out = hass.execute("journalctl -u home-assistant.service -n1 -o json-pretty --output-fields=__CURSOR") 146 assert exit == 0 147 return json.loads(out)["__CURSOR"] 148 149 150 def get_journal_since(cursor) -> str: 151 exit, out = hass.execute(f"journalctl --after-cursor='{cursor}' -u home-assistant.service") 152 assert exit == 0 153 return out 154 155 156 def get_unit_property(property) -> str: 157 exit, out = hass.execute(f"systemctl show --property={property} home-assistant.service") 158 assert exit == 0 159 return out 160 161 162 def wait_for_homeassistant(cursor): 163 hass.wait_until_succeeds(f"journalctl --after-cursor='{cursor}' -u home-assistant.service | grep -q 'Home Assistant initialized in'") 164 165 166 hass.wait_for_unit("home-assistant.service") 167 cursor = get_journal_cursor() 168 169 with subtest("Check that YAML configuration file is in place"): 170 hass.succeed("test -L ${configDir}/configuration.yaml") 171 172 with subtest("Check the lovelace config is copied because lovelaceConfigWritable = true"): 173 hass.succeed("test -f ${configDir}/ui-lovelace.yaml") 174 175 with subtest("Check that Home Assistant's web interface and API can be reached"): 176 wait_for_homeassistant(cursor) 177 hass.wait_for_open_port(8123) 178 hass.succeed("curl --fail http://localhost:8123/lovelace") 179 180 with subtest("Check that custom components get installed"): 181 hass.succeed("test -f ${configDir}/custom_components/prometheus_sensor/manifest.json") 182 hass.wait_until_succeeds("journalctl -u home-assistant.service | grep -q 'We found a custom integration prometheus_sensor which has not been tested by Home Assistant'") 183 184 with subtest("Check that lovelace modules are referenced and fetchable"): 185 hass.succeed("grep -q 'mini-graph-card-bundle.js' '${configDir}/configuration.yaml'") 186 hass.succeed("curl --fail http://localhost:8123/local/nixos-lovelace-modules/mini-graph-card-bundle.js") 187 188 with subtest("Check that optional dependencies are in the PYTHONPATH"): 189 env = get_unit_property("Environment") 190 python_path = env.split("PYTHONPATH=")[1].split()[0] 191 for package in ["colorama", "paho-mqtt", "psycopg2"]: 192 assert package in python_path, f"{package} not in PYTHONPATH" 193 194 with subtest("Check that declaratively configured components get setup"): 195 journal = get_journal_since(cursor) 196 for domain in ["emulated_hue", "wake_on_lan"]: 197 assert f"Setup of domain {domain} took" in journal, f"{domain} setup missing" 198 199 with subtest("Check that capabilities are passed for emulated_hue to bind to port 80"): 200 hass.wait_for_open_port(80) 201 hass.succeed("curl --fail http://localhost:80/description.xml") 202 203 with subtest("Check extra components are considered in systemd unit hardening"): 204 hass.succeed("systemctl show -p DeviceAllow home-assistant.service | grep -q char-ttyUSB") 205 206 with subtest("Check service reloads when configuration changes"): 207 pid = hass.succeed("systemctl show --property=MainPID home-assistant.service") 208 cursor = get_journal_cursor() 209 hass.succeed("${system}/specialisation/differentName/bin/switch-to-configuration test") 210 new_pid = hass.succeed("systemctl show --property=MainPID home-assistant.service") 211 assert pid == new_pid, "The PID of the process should not change between process reloads" 212 wait_for_homeassistant(cursor) 213 214 with subtest("Check service restarts when dependencies change"): 215 pid = new_pid 216 cursor = get_journal_cursor() 217 hass.succeed("${system}/specialisation/newFeature/bin/switch-to-configuration test") 218 new_pid = hass.succeed("systemctl show --property=MainPID home-assistant.service") 219 assert pid != new_pid, "The PID of the process should change when its PYTHONPATH changess" 220 wait_for_homeassistant(cursor) 221 222 with subtest("Check that new components get setup after restart"): 223 journal = get_journal_since(cursor) 224 for domain in ["backup"]: 225 assert f"Setup of domain {domain} took" in journal, f"{domain} setup missing" 226 227 with subtest("Check custom components and custom lovelace modules get removed"): 228 cursor = get_journal_cursor() 229 hass.succeed("${system}/specialisation/removeCustomThings/bin/switch-to-configuration test") 230 hass.fail("grep -q 'mini-graph-card-bundle.js' '${configDir}/ui-lovelace.yaml'") 231 hass.fail("test -f ${configDir}/custom_components/prometheus_sensor/manifest.json") 232 wait_for_homeassistant(cursor) 233 234 with subtest("Check that no errors were logged"): 235 hass.fail("journalctl -u home-assistant -o cat | grep -q ERROR") 236 237 with subtest("Check systemd unit hardening"): 238 hass.log(hass.succeed("systemctl cat home-assistant.service")) 239 hass.log(hass.succeed("systemd-analyze security home-assistant.service")) 240 ''; 241})