nixpkgs/nixos/tests/home-assistant.nix
Silvan Mosberger 4f0dadbf38 treewide: format all inactive Nix files
After final improvements to the official formatter implementation,
this commit now performs the first treewide reformat of Nix files using it.
This is part of the implementation of RFC 166.

Only "inactive" files are reformatted, meaning only files that
aren't being touched by any PR with activity in the past 2 months.
This is to avoid conflicts for PRs that might soon be merged.
Later we can do a full treewide reformat to get the rest,
which should not cause as many conflicts.

A CI check has already been running for some time to ensure that new and
already-formatted files are formatted, so the files being reformatted here
should also stay formatted.

This commit was automatically created and can be verified using

    nix-build a08b3a4d19.tar.gz \
      --argstr baseRev b32a094368
    result/bin/apply-formatting $NIXPKGS_PATH
2024-12-10 20:26:33 +01:00

266 lines
9.5 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 = { };
shelly = { };
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 = "info";
};
};
# 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;
};
# 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 [ ];
};
};
};
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 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 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'")
for integration in ("prometheus_sensor", "spook", "spook_inverse"):
hass.fail(f"test -f ${configDir}/custom_components/{integration}/manifest.json")
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"))
'';
}