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