2021-05-03 20:48:10 +00:00
|
|
|
import ./make-test-python.nix ({ pkgs, lib, ... }:
|
2020-04-24 23:36:52 +00:00
|
|
|
|
|
|
|
let
|
|
|
|
configDir = "/var/lib/foobar";
|
|
|
|
in {
|
|
|
|
name = "home-assistant";
|
2021-05-03 20:48:10 +00:00
|
|
|
meta.maintainers = lib.teams.home-assistant.members;
|
2020-04-24 23:36:52 +00:00
|
|
|
|
2020-06-15 15:56:04 +00:00
|
|
|
nodes.hass = { pkgs, ... }: {
|
2022-02-20 05:27:41 +00:00
|
|
|
services.postgresql = {
|
|
|
|
enable = true;
|
2024-01-02 11:29:13 +00:00
|
|
|
ensureDatabases = [ "hass" ];
|
|
|
|
ensureUsers = [{
|
|
|
|
name = "hass";
|
|
|
|
ensureDBOwnership = true;
|
|
|
|
}];
|
2022-02-20 05:27:41 +00:00
|
|
|
};
|
|
|
|
|
2020-06-15 15:56:04 +00:00
|
|
|
services.home-assistant = {
|
|
|
|
enable = true;
|
2022-02-20 05:27:41 +00:00
|
|
|
inherit configDir;
|
|
|
|
|
2023-02-22 10:55:15 +00:00
|
|
|
# provide dependencies through package overrides
|
2021-12-06 16:07:01 +00:00
|
|
|
package = (pkgs.home-assistant.override {
|
2022-02-20 05:27:41 +00:00
|
|
|
extraPackages = ps: with ps; [
|
|
|
|
colorama
|
|
|
|
];
|
2023-02-22 10:55:15 +00:00
|
|
|
extraComponents = [
|
|
|
|
# test char-tty device allow propagation into the service
|
|
|
|
"zha"
|
|
|
|
];
|
2021-12-06 16:07:01 +00:00
|
|
|
});
|
2022-02-20 05:27:41 +00:00
|
|
|
|
2023-02-22 10:55:15 +00:00
|
|
|
# provide component dependencies explicitly from the module
|
2022-02-20 05:27:41 +00:00
|
|
|
extraComponents = [
|
2023-02-22 10:55:15 +00:00
|
|
|
"mqtt"
|
2022-02-20 05:27:41 +00:00
|
|
|
];
|
|
|
|
|
2023-02-22 10:55:15 +00:00
|
|
|
# provide package for postgresql support
|
2022-02-20 05:27:41 +00:00
|
|
|
extraPackages = python3Packages: with python3Packages; [
|
|
|
|
psycopg2
|
|
|
|
];
|
|
|
|
|
2023-11-16 04:20:00 +00:00
|
|
|
# test loading custom components
|
|
|
|
customComponents = with pkgs.home-assistant-custom-components; [
|
2024-01-02 11:29:13 +00:00
|
|
|
prometheus_sensor
|
2024-06-24 18:47:55 +00:00
|
|
|
# tests loading multiple components from a single package
|
|
|
|
spook
|
2023-11-16 04:20:00 +00:00
|
|
|
];
|
|
|
|
|
|
|
|
# test loading lovelace modules
|
|
|
|
customLovelaceModules = with pkgs.home-assistant-custom-lovelace-modules; [
|
|
|
|
mini-graph-card
|
|
|
|
];
|
|
|
|
|
2020-06-15 15:56:04 +00:00
|
|
|
config = {
|
|
|
|
homeassistant = {
|
|
|
|
name = "Home";
|
|
|
|
time_zone = "UTC";
|
|
|
|
latitude = "0.0";
|
|
|
|
longitude = "0.0";
|
|
|
|
elevation = 0;
|
2020-04-24 23:36:52 +00:00
|
|
|
};
|
2022-02-20 05:27:41 +00:00
|
|
|
|
|
|
|
# configure the recorder component to use the postgresql db
|
|
|
|
recorder.db_url = "postgresql://@/hass";
|
|
|
|
|
|
|
|
# we can't load default_config, because the updater requires
|
|
|
|
# network access and would cause an error, so load frontend
|
|
|
|
# here explicitly.
|
|
|
|
# https://www.home-assistant.io/integrations/frontend/
|
2020-06-15 15:56:04 +00:00
|
|
|
frontend = {};
|
2022-02-20 05:27:41 +00:00
|
|
|
|
2023-04-29 16:46:19 +00:00
|
|
|
# include some popular integrations, that absolutely shouldn't break
|
|
|
|
knx = {};
|
|
|
|
shelly = {};
|
|
|
|
zha = {};
|
|
|
|
|
2022-02-20 05:27:41 +00:00
|
|
|
# set up a wake-on-lan switch to test capset capability required
|
|
|
|
# for the ping suid wrapper
|
|
|
|
# https://www.home-assistant.io/integrations/wake_on_lan/
|
|
|
|
switch = [ {
|
2022-01-26 04:04:25 +00:00
|
|
|
platform = "wake_on_lan";
|
|
|
|
mac = "00:11:22:33:44:55";
|
|
|
|
host = "127.0.0.1";
|
2022-02-20 05:27:41 +00:00
|
|
|
} ];
|
|
|
|
|
|
|
|
# test component-based capability assignment (CAP_NET_BIND_SERVICE)
|
|
|
|
# https://www.home-assistant.io/integrations/emulated_hue/
|
2021-05-03 20:48:10 +00:00
|
|
|
emulated_hue = {
|
|
|
|
host_ip = "127.0.0.1";
|
|
|
|
listen_port = 80;
|
|
|
|
};
|
2022-02-20 05:27:41 +00:00
|
|
|
|
|
|
|
# https://www.home-assistant.io/integrations/logger/
|
2020-06-15 15:56:04 +00:00
|
|
|
logger = {
|
|
|
|
default = "info";
|
|
|
|
};
|
|
|
|
};
|
2022-02-20 05:27:41 +00:00
|
|
|
|
|
|
|
# configure the sample lovelace dashboard
|
2020-06-15 15:56:04 +00:00
|
|
|
lovelaceConfig = {
|
|
|
|
title = "My Awesome Home";
|
|
|
|
views = [{
|
|
|
|
title = "Example";
|
|
|
|
cards = [{
|
|
|
|
type = "markdown";
|
|
|
|
title = "Lovelace";
|
|
|
|
content = "Welcome to your **Lovelace UI**.";
|
|
|
|
}];
|
|
|
|
}];
|
2020-04-24 23:36:52 +00:00
|
|
|
};
|
2020-06-15 15:56:04 +00:00
|
|
|
lovelaceConfigWritable = true;
|
|
|
|
};
|
2022-06-26 10:26:21 +00:00
|
|
|
|
|
|
|
# Cause a configuration change inside `configuration.yml` and verify that the process is being reloaded.
|
|
|
|
specialisation.differentName = {
|
|
|
|
inheritParentConfig = true;
|
|
|
|
configuration.services.home-assistant.config.homeassistant.name = lib.mkForce "Test Home";
|
|
|
|
};
|
|
|
|
|
|
|
|
# Cause a configuration change that requires a service restart as we added a new runtime dependency
|
|
|
|
specialisation.newFeature = {
|
|
|
|
inheritParentConfig = true;
|
2023-04-29 16:46:19 +00:00
|
|
|
configuration.services.home-assistant.config.backup = {};
|
2022-06-26 10:26:21 +00:00
|
|
|
};
|
2023-11-16 04:20:00 +00:00
|
|
|
|
|
|
|
specialisation.removeCustomThings = {
|
|
|
|
inheritParentConfig = true;
|
|
|
|
configuration.services.home-assistant = {
|
|
|
|
customComponents = lib.mkForce [];
|
|
|
|
customLovelaceModules = lib.mkForce [];
|
|
|
|
};
|
|
|
|
};
|
2020-04-24 23:36:52 +00:00
|
|
|
};
|
|
|
|
|
2022-06-26 10:26:21 +00:00
|
|
|
testScript = { nodes, ... }: let
|
2023-02-22 10:55:15 +00:00
|
|
|
system = nodes.hass.system.build.toplevel;
|
2022-06-26 10:26:21 +00:00
|
|
|
in
|
|
|
|
''
|
2022-07-14 12:49:19 +00:00
|
|
|
import json
|
2022-02-20 05:27:41 +00:00
|
|
|
|
2020-04-24 23:36:52 +00:00
|
|
|
start_all()
|
2022-02-20 05:27:41 +00:00
|
|
|
|
|
|
|
|
2023-02-22 10:55:15 +00:00
|
|
|
def get_journal_cursor() -> str:
|
|
|
|
exit, out = hass.execute("journalctl -u home-assistant.service -n1 -o json-pretty --output-fields=__CURSOR")
|
2022-07-14 12:49:19 +00:00
|
|
|
assert exit == 0
|
|
|
|
return json.loads(out)["__CURSOR"]
|
|
|
|
|
|
|
|
|
2023-02-22 10:55:15 +00:00
|
|
|
def get_journal_since(cursor) -> str:
|
|
|
|
exit, out = hass.execute(f"journalctl --after-cursor='{cursor}' -u home-assistant.service")
|
|
|
|
assert exit == 0
|
|
|
|
return out
|
|
|
|
|
|
|
|
|
|
|
|
def get_unit_property(property) -> str:
|
|
|
|
exit, out = hass.execute(f"systemctl show --property={property} home-assistant.service")
|
|
|
|
assert exit == 0
|
|
|
|
return out
|
|
|
|
|
|
|
|
|
|
|
|
def wait_for_homeassistant(cursor):
|
|
|
|
hass.wait_until_succeeds(f"journalctl --after-cursor='{cursor}' -u home-assistant.service | grep -q 'Home Assistant initialized in'")
|
2022-07-14 12:49:19 +00:00
|
|
|
|
|
|
|
|
2020-04-24 23:36:52 +00:00
|
|
|
hass.wait_for_unit("home-assistant.service")
|
2023-02-22 10:55:15 +00:00
|
|
|
cursor = get_journal_cursor()
|
2022-02-20 05:27:41 +00:00
|
|
|
|
2020-04-24 23:36:52 +00:00
|
|
|
with subtest("Check that YAML configuration file is in place"):
|
|
|
|
hass.succeed("test -L ${configDir}/configuration.yaml")
|
2022-02-20 05:27:41 +00:00
|
|
|
|
|
|
|
with subtest("Check the lovelace config is copied because lovelaceConfigWritable = true"):
|
2020-04-24 23:36:52 +00:00
|
|
|
hass.succeed("test -f ${configDir}/ui-lovelace.yaml")
|
2022-02-20 05:27:41 +00:00
|
|
|
|
2020-04-24 23:36:52 +00:00
|
|
|
with subtest("Check that Home Assistant's web interface and API can be reached"):
|
2023-02-22 10:55:15 +00:00
|
|
|
wait_for_homeassistant(cursor)
|
2020-04-24 23:36:52 +00:00
|
|
|
hass.wait_for_open_port(8123)
|
2020-05-29 06:06:01 +00:00
|
|
|
hass.succeed("curl --fail http://localhost:8123/lovelace")
|
2022-02-20 05:27:41 +00:00
|
|
|
|
2023-11-16 04:20:00 +00:00
|
|
|
with subtest("Check that custom components get installed"):
|
|
|
|
hass.succeed("test -f ${configDir}/custom_components/prometheus_sensor/manifest.json")
|
2024-06-24 18:47:55 +00:00
|
|
|
for integration in ("prometheus_sensor", "spook", "spook_inverse"):
|
|
|
|
hass.wait_until_succeeds(f"journalctl -u home-assistant.service | grep -q 'We found a custom integration {integration} which has not been tested by Home Assistant'")
|
2023-11-16 04:20:00 +00:00
|
|
|
|
|
|
|
with subtest("Check that lovelace modules are referenced and fetchable"):
|
2024-01-02 11:29:13 +00:00
|
|
|
hass.succeed("grep -q 'mini-graph-card-bundle.js' '${configDir}/configuration.yaml'")
|
2023-11-16 04:20:00 +00:00
|
|
|
hass.succeed("curl --fail http://localhost:8123/local/nixos-lovelace-modules/mini-graph-card-bundle.js")
|
|
|
|
|
2023-02-22 10:55:15 +00:00
|
|
|
with subtest("Check that optional dependencies are in the PYTHONPATH"):
|
|
|
|
env = get_unit_property("Environment")
|
|
|
|
python_path = env.split("PYTHONPATH=")[1].split()[0]
|
|
|
|
for package in ["colorama", "paho-mqtt", "psycopg2"]:
|
|
|
|
assert package in python_path, f"{package} not in PYTHONPATH"
|
|
|
|
|
|
|
|
with subtest("Check that declaratively configured components get setup"):
|
|
|
|
journal = get_journal_since(cursor)
|
|
|
|
for domain in ["emulated_hue", "wake_on_lan"]:
|
|
|
|
assert f"Setup of domain {domain} took" in journal, f"{domain} setup missing"
|
|
|
|
|
2021-05-03 20:48:10 +00:00
|
|
|
with subtest("Check that capabilities are passed for emulated_hue to bind to port 80"):
|
|
|
|
hass.wait_for_open_port(80)
|
|
|
|
hass.succeed("curl --fail http://localhost:80/description.xml")
|
2022-02-20 05:27:41 +00:00
|
|
|
|
2021-12-06 16:07:01 +00:00
|
|
|
with subtest("Check extra components are considered in systemd unit hardening"):
|
|
|
|
hass.succeed("systemctl show -p DeviceAllow home-assistant.service | grep -q char-ttyUSB")
|
2022-02-20 05:27:41 +00:00
|
|
|
|
2022-06-26 10:26:21 +00:00
|
|
|
with subtest("Check service reloads when configuration changes"):
|
2023-02-22 10:55:15 +00:00
|
|
|
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)
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
with subtest("Check that new components get setup after restart"):
|
|
|
|
journal = get_journal_since(cursor)
|
2023-07-15 17:15:38 +00:00
|
|
|
for domain in ["backup"]:
|
2023-02-22 10:55:15 +00:00
|
|
|
assert f"Setup of domain {domain} took" in journal, f"{domain} setup missing"
|
2020-04-24 23:36:52 +00:00
|
|
|
|
2023-11-16 04:20:00 +00:00
|
|
|
with subtest("Check custom components and custom lovelace modules get removed"):
|
|
|
|
cursor = get_journal_cursor()
|
|
|
|
hass.succeed("${system}/specialisation/removeCustomThings/bin/switch-to-configuration test")
|
|
|
|
hass.fail("grep -q 'mini-graph-card-bundle.js' '${configDir}/ui-lovelace.yaml'")
|
2024-06-24 18:47:55 +00:00
|
|
|
for integration in ("prometheus_sensor", "spook", "spook_inverse"):
|
|
|
|
hass.fail(f"test -f ${configDir}/custom_components/{integration}/manifest.json")
|
2023-11-16 04:20:00 +00:00
|
|
|
wait_for_homeassistant(cursor)
|
|
|
|
|
2020-04-24 23:36:52 +00:00
|
|
|
with subtest("Check that no errors were logged"):
|
2023-02-22 10:55:15 +00:00
|
|
|
hass.fail("journalctl -u home-assistant -o cat | grep -q ERROR")
|
2020-06-15 15:56:04 +00:00
|
|
|
|
2021-05-03 20:48:10 +00:00
|
|
|
with subtest("Check systemd unit hardening"):
|
2022-02-20 05:27:41 +00:00
|
|
|
hass.log(hass.succeed("systemctl cat home-assistant.service"))
|
2021-05-03 20:48:10 +00:00
|
|
|
hass.log(hass.succeed("systemd-analyze security home-assistant.service"))
|
2020-04-24 23:36:52 +00:00
|
|
|
'';
|
|
|
|
})
|