nixos/home-assistant: add customComponents support

Allows passing custom component packages, that get installed into
home-assistant's state directory.
Python depedencies, that are propagated from the custom component
get passed into `extraPackages`, so they are available to
home-assistant at runtime.

This is implemented in a way, that allows coexistence with custom
components not managed through the NixOS module.
This commit is contained in:
Martin Weinelt 2022-02-01 03:10:31 +01:00
parent 047b9665f2
commit f30192ae6f
No known key found for this signature in database
GPG Key ID: 87C1E9888F856759
2 changed files with 95 additions and 2 deletions

View File

@ -63,7 +63,9 @@ let
# Respect overrides that already exist in the passed package and
# concat it with values passed via the module.
extraComponents = oldArgs.extraComponents or [] ++ extraComponents;
extraPackages = ps: (oldArgs.extraPackages or (_: []) ps) ++ (cfg.extraPackages ps);
extraPackages = ps: (oldArgs.extraPackages or (_: []) ps)
++ (cfg.extraPackages ps)
++ (lib.concatMap (component: component.propagatedBuildInputs or []) cfg.customComponents);
}));
# Create a directory that holds all lovelace modules
@ -152,6 +154,21 @@ in {
'';
};
customComponents = mkOption {
type = types.listOf types.package;
default = [];
example = literalExpression ''
with pkgs.home-assistant-custom-components; [
prometheus-sensor
];
'';
description = lib.mdDoc ''
List of custom component packages to install.
Available components can be found below `pkgs.home-assistant-custom-components`.
'';
};
customLovelaceModules = mkOption {
type = types.listOf types.package;
default = [];
@ -449,10 +466,29 @@ in {
'' else ''
rm -f "${cfg.configDir}/www/nixos-lovelace-modules"
'';
copyCustomComponents = ''
mkdir -p "${cfg.configDir}/custom_components"
# remove components symlinked in from below the /nix/store
components="$(find "${cfg.configDir}/custom_components" -maxdepth 1 -type l)"
for component in "$components"; do
if [[ "$(readlink "$component")" =~ ^${escapeShellArg builtins.storeDir} ]]; then
rm "$component"
fi
done
# recreate symlinks for desired components
declare -a components=(${escapeShellArgs cfg.customComponents})
for component in "''${components[@]}"; do
path="$(dirname $(find "$component" -name "manifest.json"))"
ln -fns "$path" "${cfg.configDir}/custom_components/"
done
'';
in
(optionalString (cfg.config != null) copyConfig) +
(optionalString (cfg.lovelaceConfig != null) copyLovelaceConfig) +
copyCustomLovelaceModules
copyCustomLovelaceModules +
copyCustomComponents
;
environment.PYTHONPATH = package.pythonPath;
serviceConfig = let

View File

@ -0,0 +1,57 @@
# Packaging guidelines
## buildHomeAssistantComponent
Custom components should be packaged using the
`buildHomeAssistantComponent` function, that is provided at top-level.
It builds upon `buildPythonPackage` but uses a custom install and check
phase.
Python runtime dependencies can be directly consumed as unqualified
function arguments. Pass them into `propagatedBuildInputs`, for them to
be available to Home Assistant.
Out-of-tree components need to use python packages from
`home-assistant.python.pkgs` as to not introduce conflicting package
versions into the Python environment.
**Example Boilerplate:**
```nix
{ lib
, buildHomeAssistantcomponent
, fetchFromGitHub
}:
buildHomeAssistantComponent {
# pname, version
src = fetchFromGithub {
# owner, repo, rev, hash
};
propagatedBuildInputs = [
# python requirements, as specified in manifest.json
];
meta = with lib; {
# changelog, description, homepage, license, maintainers
}
}
## Package name normalization
Apply the same normalization rules as defined for python packages in
[PEP503](https://peps.python.org/pep-0503/#normalized-names).
The name should be lowercased and dots, underlines or multiple
dashes should all be replaced by a single dash.
## Manifest check
The `buildHomeAssistantComponent` builder uses a hook to check whether
the dependencies specified in the `manifest.json` are present and
inside the specified version range.
There shouldn't be a need to disable this hook, but you can set
`dontCheckManifest` to `true` in the derivation to achieve that.