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