aboutsummaryrefslogtreecommitdiff
path: root/nixpkgs/nixos/modules/services/misc/home-assistant.nix
diff options
context:
space:
mode:
Diffstat (limited to 'nixpkgs/nixos/modules/services/misc/home-assistant.nix')
-rw-r--r--nixpkgs/nixos/modules/services/misc/home-assistant.nix250
1 files changed, 250 insertions, 0 deletions
diff --git a/nixpkgs/nixos/modules/services/misc/home-assistant.nix b/nixpkgs/nixos/modules/services/misc/home-assistant.nix
new file mode 100644
index 00000000000..74702c97f55
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/home-assistant.nix
@@ -0,0 +1,250 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.home-assistant;
+
+ # cfg.config != null can be assumed here
+ configJSON = pkgs.writeText "configuration.json"
+ (builtins.toJSON (if cfg.applyDefaultConfig then
+ (recursiveUpdate defaultConfig cfg.config) else cfg.config));
+ configFile = pkgs.runCommand "configuration.yaml" { preferLocalBuild = true; } ''
+ ${pkgs.remarshal}/bin/json2yaml -i ${configJSON} -o $out
+ '';
+
+ lovelaceConfigJSON = pkgs.writeText "ui-lovelace.json"
+ (builtins.toJSON cfg.lovelaceConfig);
+ lovelaceConfigFile = pkgs.runCommand "ui-lovelace.yaml" { preferLocalBuild = true; } ''
+ ${pkgs.remarshal}/bin/json2yaml -i ${lovelaceConfigJSON} -o $out
+ '';
+
+ availableComponents = cfg.package.availableComponents;
+
+ usedPlatforms = config:
+ if isAttrs config then
+ optional (config ? platform) config.platform
+ ++ concatMap usedPlatforms (attrValues config)
+ else if isList config then
+ concatMap usedPlatforms config
+ else [ ];
+
+ # Given a component "platform", looks up whether it is used in the config
+ # as `platform = "platform";`.
+ #
+ # For example, the component mqtt.sensor is used as follows:
+ # config.sensor = [ {
+ # platform = "mqtt";
+ # ...
+ # } ];
+ useComponentPlatform = component: elem component (usedPlatforms cfg.config);
+
+ # Returns whether component is used in config
+ useComponent = component:
+ hasAttrByPath (splitString "." component) cfg.config
+ || useComponentPlatform component;
+
+ # List of components used in config
+ extraComponents = filter useComponent availableComponents;
+
+ package = if (cfg.autoExtraComponents && cfg.config != null)
+ then (cfg.package.override { inherit extraComponents; })
+ else cfg.package;
+
+ # If you are changing this, please update the description in applyDefaultConfig
+ defaultConfig = {
+ homeassistant.time_zone = config.time.timeZone;
+ http.server_port = cfg.port;
+ } // optionalAttrs (cfg.lovelaceConfig != null) {
+ lovelace.mode = "yaml";
+ };
+
+in {
+ meta.maintainers = with maintainers; [ dotlambda ];
+
+ options.services.home-assistant = {
+ enable = mkEnableOption "Home Assistant";
+
+ configDir = mkOption {
+ default = "/var/lib/hass";
+ type = types.path;
+ description = "The config directory, where your <filename>configuration.yaml</filename> is located.";
+ };
+
+ port = mkOption {
+ default = 8123;
+ type = types.int;
+ description = "The port on which to listen.";
+ };
+
+ applyDefaultConfig = mkOption {
+ default = true;
+ type = types.bool;
+ description = ''
+ Setting this option enables a few configuration options for HA based on NixOS configuration (such as time zone) to avoid having to manually specify configuration we already have.
+ </para>
+ <para>
+ Currently one side effect of enabling this is that the <literal>http</literal> component will be enabled.
+ </para>
+ <para>
+ This only takes effect if <literal>config != null</literal> in order to ensure that a manually managed <filename>configuration.yaml</filename> is not overwritten.
+ '';
+ };
+
+ config = mkOption {
+ default = null;
+ type = with types; nullOr attrs;
+ example = literalExample ''
+ {
+ homeassistant = {
+ name = "Home";
+ time_zone = "UTC";
+ };
+ frontend = { };
+ http = { };
+ feedreader.urls = [ "https://nixos.org/blogs.xml" ];
+ }
+ '';
+ description = ''
+ Your <filename>configuration.yaml</filename> as a Nix attribute set.
+ Beware that setting this option will delete your previous <filename>configuration.yaml</filename>.
+ '';
+ };
+
+ configWritable = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Whether to make <filename>configuration.yaml</filename> writable.
+ This only has an effect if <option>config</option> is set.
+ This will allow you to edit it from Home Assistant's web interface.
+ However, bear in mind that it will be overwritten at every start of the service.
+ '';
+ };
+
+ lovelaceConfig = mkOption {
+ default = null;
+ type = with types; nullOr attrs;
+ # from https://www.home-assistant.io/lovelace/yaml-mode/
+ example = literalExample ''
+ {
+ title = "My Awesome Home";
+ views = [ {
+ title = "Example";
+ cards = [ {
+ type = "markdown";
+ title = "Lovelace";
+ content = "Welcome to your **Lovelace UI**.";
+ } ];
+ } ];
+ }
+ '';
+ description = ''
+ Your <filename>ui-lovelace.yaml</filename> as a Nix attribute set.
+ Setting this option will automatically add
+ <literal>lovelace.mode = "yaml";</literal> to your <option>config</option>.
+ Beware that setting this option will delete your previous <filename>ui-lovelace.yaml</filename>
+ '';
+ };
+
+ lovelaceConfigWritable = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Whether to make <filename>ui-lovelace.yaml</filename> writable.
+ This only has an effect if <option>lovelaceConfig</option> is set.
+ This will allow you to edit it from Home Assistant's web interface.
+ However, bear in mind that it will be overwritten at every start of the service.
+ '';
+ };
+
+ package = mkOption {
+ default = pkgs.home-assistant;
+ defaultText = "pkgs.home-assistant";
+ type = types.package;
+ example = literalExample ''
+ pkgs.home-assistant.override {
+ extraPackages = ps: with ps; [ colorlog ];
+ }
+ '';
+ description = ''
+ Home Assistant package to use.
+ Override <literal>extraPackages</literal> or <literal>extraComponents</literal> in order to add additional dependencies.
+ If you specify <option>config</option> and do not set <option>autoExtraComponents</option>
+ to <literal>false</literal>, overriding <literal>extraComponents</literal> will have no effect.
+ '';
+ };
+
+ autoExtraComponents = mkOption {
+ default = true;
+ type = types.bool;
+ description = ''
+ If set to <literal>true</literal>, the components used in <literal>config</literal>
+ are set as the specified package's <literal>extraComponents</literal>.
+ This in turn adds all packaged dependencies to the derivation.
+ You might still see import errors in your log.
+ In this case, you will need to package the necessary dependencies yourself
+ or ask for someone else to package them.
+ If a dependency is packaged but not automatically added to this list,
+ you might need to specify it in <literal>extraPackages</literal>.
+ '';
+ };
+
+ openFirewall = mkOption {
+ default = false;
+ type = types.bool;
+ description = "Whether to open the firewall for the specified port.";
+ };
+ };
+
+ config = mkIf cfg.enable {
+ networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [ cfg.port ];
+
+ systemd.services.home-assistant = {
+ description = "Home Assistant";
+ after = [ "network.target" ];
+ preStart = optionalString (cfg.config != null) (if cfg.configWritable then ''
+ cp --no-preserve=mode ${configFile} "${cfg.configDir}/configuration.yaml"
+ '' else ''
+ rm -f "${cfg.configDir}/configuration.yaml"
+ ln -s ${configFile} "${cfg.configDir}/configuration.yaml"
+ '') + optionalString (cfg.lovelaceConfig != null) (if cfg.lovelaceConfigWritable then ''
+ cp --no-preserve=mode ${lovelaceConfigFile} "${cfg.configDir}/ui-lovelace.yaml"
+ '' else ''
+ rm -f "${cfg.configDir}/ui-lovelace.yaml"
+ ln -s ${lovelaceConfigFile} "${cfg.configDir}/ui-lovelace.yaml"
+ '');
+ serviceConfig = {
+ ExecStart = "${package}/bin/hass --config '${cfg.configDir}'";
+ User = "hass";
+ Group = "hass";
+ Restart = "on-failure";
+ ProtectSystem = "strict";
+ ReadWritePaths = "${cfg.configDir}";
+ KillSignal = "SIGINT";
+ PrivateTmp = true;
+ RemoveIPC = true;
+ AmbientCapabilities = "cap_net_raw,cap_net_admin+eip";
+ };
+ path = [
+ "/run/wrappers" # needed for ping
+ ];
+ };
+
+ systemd.targets.home-assistant = rec {
+ description = "Home Assistant";
+ wantedBy = [ "multi-user.target" ];
+ wants = [ "home-assistant.service" ];
+ after = wants;
+ };
+
+ users.users.hass = {
+ home = cfg.configDir;
+ createHome = true;
+ group = "hass";
+ uid = config.ids.uids.hass;
+ };
+
+ users.groups.hass.gid = config.ids.gids.hass;
+ };
+}