mirror of
https://github.com/NixOS/nixpkgs.git
synced 2025-06-09 19:13:26 +03:00

Remove the shelly component, as it now has a dependency on a bluetooth setup, which is not easy to provide in the test environment. Enabling `hardware.bluetooth.enable` is insufficient. Turn up the loglevel to `debug`, so we can still see which integrations get set up. This is used for for checking whether all our ways of setting up integrations still work.
282 lines
10 KiB
Nix
282 lines
10 KiB
Nix
{
|
|
lib,
|
|
...
|
|
}:
|
|
|
|
let
|
|
configDir = "/var/lib/foobar";
|
|
in
|
|
{
|
|
name = "home-assistant";
|
|
meta.maintainers = lib.teams.home-assistant.members;
|
|
|
|
nodes.hass =
|
|
{ pkgs, ... }:
|
|
{
|
|
services.postgresql = {
|
|
enable = true;
|
|
ensureDatabases = [ "hass" ];
|
|
ensureUsers = [
|
|
{
|
|
name = "hass";
|
|
ensureDBOwnership = true;
|
|
}
|
|
];
|
|
};
|
|
|
|
services.home-assistant = {
|
|
enable = true;
|
|
inherit configDir;
|
|
|
|
# provide dependencies through package overrides
|
|
package = (
|
|
pkgs.home-assistant.override {
|
|
extraPackages =
|
|
ps: with ps; [
|
|
colorama
|
|
];
|
|
extraComponents = [
|
|
# test char-tty device allow propagation into the service
|
|
"zha"
|
|
];
|
|
}
|
|
);
|
|
|
|
# provide component dependencies explicitly from the module
|
|
extraComponents = [
|
|
"mqtt"
|
|
];
|
|
|
|
# provide package for postgresql support
|
|
extraPackages =
|
|
python3Packages: with python3Packages; [
|
|
psycopg2
|
|
];
|
|
|
|
# test loading custom components
|
|
customComponents = with pkgs.home-assistant-custom-components; [
|
|
prometheus_sensor
|
|
# tests loading multiple components from a single package
|
|
spook
|
|
];
|
|
|
|
# test loading lovelace modules
|
|
customLovelaceModules = with pkgs.home-assistant-custom-lovelace-modules; [
|
|
mini-graph-card
|
|
];
|
|
|
|
config = {
|
|
homeassistant = {
|
|
name = "Home";
|
|
time_zone = "UTC";
|
|
latitude = "0.0";
|
|
longitude = "0.0";
|
|
elevation = 0;
|
|
};
|
|
|
|
# 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/
|
|
frontend = { };
|
|
|
|
# include some popular integrations, that absolutely shouldn't break
|
|
knx = { };
|
|
zha = { };
|
|
|
|
# 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 = [
|
|
{
|
|
platform = "wake_on_lan";
|
|
mac = "00:11:22:33:44:55";
|
|
host = "127.0.0.1";
|
|
}
|
|
];
|
|
|
|
# test component-based capability assignment (CAP_NET_BIND_SERVICE)
|
|
# https://www.home-assistant.io/integrations/emulated_hue/
|
|
emulated_hue = {
|
|
host_ip = "127.0.0.1";
|
|
listen_port = 80;
|
|
};
|
|
|
|
# https://www.home-assistant.io/integrations/logger/
|
|
logger = {
|
|
default = "debug";
|
|
};
|
|
};
|
|
|
|
# configure the sample lovelace dashboard
|
|
lovelaceConfig = {
|
|
title = "My Awesome Home";
|
|
views = [
|
|
{
|
|
title = "Example";
|
|
cards = [
|
|
{
|
|
type = "markdown";
|
|
title = "Lovelace";
|
|
content = "Welcome to your **Lovelace UI**.";
|
|
}
|
|
];
|
|
}
|
|
];
|
|
};
|
|
lovelaceConfigWritable = true;
|
|
|
|
blueprints.automation = [
|
|
(pkgs.fetchurl {
|
|
url = "https://github.com/home-assistant/core/raw/2025.1.4/homeassistant/components/automation/blueprints/motion_light.yaml";
|
|
hash = "sha256-4HrDX65ycBMfEY2nZ7A25/d3ZnIHdpHZ+80Cblp+P5w=";
|
|
})
|
|
];
|
|
blueprints.template = [
|
|
"${pkgs.home-assistant.src}/homeassistant/components/template/blueprints/inverted_binary_sensor.yaml"
|
|
];
|
|
};
|
|
|
|
# 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;
|
|
configuration.services.home-assistant.config.prometheus = { };
|
|
};
|
|
|
|
specialisation.removeCustomThings = {
|
|
inheritParentConfig = true;
|
|
configuration.services.home-assistant = {
|
|
customComponents = lib.mkForce [ ];
|
|
customLovelaceModules = lib.mkForce [ ];
|
|
blueprints.automation = lib.mkForce [ ];
|
|
blueprints.template = lib.mkForce [ ];
|
|
};
|
|
};
|
|
};
|
|
|
|
testScript =
|
|
{ nodes, ... }:
|
|
let
|
|
system = nodes.hass.system.build.toplevel;
|
|
in
|
|
''
|
|
import json
|
|
|
|
start_all()
|
|
|
|
|
|
def get_journal_cursor() -> str:
|
|
exit, out = hass.execute("journalctl -u home-assistant.service -n1 -o json-pretty --output-fields=__CURSOR")
|
|
assert exit == 0
|
|
return json.loads(out)["__CURSOR"]
|
|
|
|
|
|
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'")
|
|
|
|
|
|
hass.wait_for_unit("home-assistant.service")
|
|
cursor = get_journal_cursor()
|
|
|
|
with subtest("Check that YAML configuration file is in place"):
|
|
hass.succeed("test -L ${configDir}/configuration.yaml")
|
|
|
|
with subtest("Check the lovelace config is copied because lovelaceConfigWritable = true"):
|
|
hass.succeed("test -f ${configDir}/ui-lovelace.yaml")
|
|
|
|
with subtest("Check that Home Assistant's web interface and API can be reached"):
|
|
wait_for_homeassistant(cursor)
|
|
hass.wait_for_open_port(8123)
|
|
hass.succeed("curl --fail http://localhost:8123/lovelace")
|
|
|
|
with subtest("Check that custom components get installed"):
|
|
hass.succeed("test -f ${configDir}/custom_components/prometheus_sensor/manifest.json")
|
|
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'")
|
|
|
|
with subtest("Check that lovelace modules are referenced and fetchable"):
|
|
hass.succeed("grep -q 'mini-graph-card-bundle.js' '${configDir}/configuration.yaml'")
|
|
hass.succeed("curl --fail http://localhost:8123/local/nixos-lovelace-modules/mini-graph-card-bundle.js")
|
|
|
|
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"
|
|
|
|
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")
|
|
|
|
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 that blueprints are installed"):
|
|
hass.succeed("test -L '${configDir}/blueprints/automation/motion_light.yaml'")
|
|
hass.succeed("test -L '${configDir}/blueprints/template/inverted_binary_sensor.yaml'")
|
|
|
|
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")
|
|
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")
|
|
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)
|
|
for domain in ["prometheus"]:
|
|
assert f"Setup of domain {domain} took" in journal, f"{domain} setup missing"
|
|
|
|
with subtest("Check custom components, custom lovelace modules, and blueprints 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'")
|
|
for integration in ("prometheus_sensor", "spook", "spook_inverse"):
|
|
hass.fail(f"test -f ${configDir}/custom_components/{integration}/manifest.json")
|
|
hass.fail("test -e '${configDir}/blueprints/automation/motion_light.yaml'")
|
|
hass.fail("test -e '${configDir}/blueprints/template/inverted_binary_sensor.yaml'")
|
|
wait_for_homeassistant(cursor)
|
|
|
|
with subtest("Check that no errors were logged"):
|
|
hass.fail("journalctl -u home-assistant -o cat | grep -q ERROR")
|
|
|
|
with subtest("Check systemd unit hardening"):
|
|
hass.log(hass.succeed("systemctl cat home-assistant.service"))
|
|
hass.log(hass.succeed("systemd-analyze security home-assistant.service"))
|
|
'';
|
|
}
|