diff --git a/nixos/modules/services/home-automation/home-assistant.nix b/nixos/modules/services/home-automation/home-assistant.nix index dc5eb8401212..54fd3e17292f 100644 --- a/nixos/modules/services/home-automation/home-assistant.nix +++ b/nixos/modules/services/home-automation/home-assistant.nix @@ -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 diff --git a/pkgs/servers/home-assistant/custom-components/README.md b/pkgs/servers/home-assistant/custom-components/README.md new file mode 100644 index 000000000000..a7244b25c173 --- /dev/null +++ b/pkgs/servers/home-assistant/custom-components/README.md @@ -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.