1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6 cfg = config.services.home-assistant;
7
8 configFile = pkgs.writeText "configuration.yaml" (builtins.toJSON cfg.config);
9
10 availableComponents = pkgs.home-assistant.availableComponents;
11
12 # Given component "parentConfig.platform", returns whether config.parentConfig
13 # is a list containing a set with set.platform == "platform".
14 #
15 # For example, the component sensor.luftdaten is used as follows:
16 # config.sensor = [ {
17 # platform = "luftdaten";
18 # ...
19 # } ];
20 useComponentPlatform = component:
21 let
22 path = splitString "." component;
23 parentConfig = attrByPath (init path) null cfg.config;
24 platform = last path;
25 in isList parentConfig && any
26 (item: item.platform or null == platform)
27 parentConfig;
28
29 # Returns whether component is used in config
30 useComponent = component:
31 hasAttrByPath (splitString "." component) cfg.config
32 || useComponentPlatform component;
33
34 # List of components used in config
35 extraComponents = filter useComponent availableComponents;
36
37 package = if cfg.autoExtraComponents
38 then (cfg.package.override { inherit extraComponents; })
39 else cfg.package;
40
41in {
42 meta.maintainers = with maintainers; [ dotlambda ];
43
44 options.services.home-assistant = {
45 enable = mkEnableOption "Home Assistant";
46
47 configDir = mkOption {
48 default = "/var/lib/hass";
49 type = types.path;
50 description = "The config directory, where your <filename>configuration.yaml</filename> is located.";
51 };
52
53 config = mkOption {
54 default = null;
55 type = with types; nullOr attrs;
56 example = literalExample ''
57 {
58 homeassistant = {
59 name = "Home";
60 time_zone = "UTC";
61 };
62 frontend = { };
63 http = { };
64 feedreader.urls = [ "https://nixos.org/blogs.xml" ];
65 }
66 '';
67 description = ''
68 Your <filename>configuration.yaml</filename> as a Nix attribute set.
69 Beware that setting this option will delete your previous <filename>configuration.yaml</filename>.
70 '';
71 };
72
73 package = mkOption {
74 default = pkgs.home-assistant;
75 defaultText = "pkgs.home-assistant";
76 type = types.package;
77 example = literalExample ''
78 pkgs.home-assistant.override {
79 extraPackages = ps: with ps; [ colorlog ];
80 }
81 '';
82 description = ''
83 Home Assistant package to use.
84 Override <literal>extraPackages</literal> in order to add additional dependencies.
85 '';
86 };
87
88 autoExtraComponents = mkOption {
89 default = true;
90 type = types.bool;
91 description = ''
92 If set to <literal>true</literal>, the components used in <literal>config</literal>
93 are set as the specified package's <literal>extraComponents</literal>.
94 This in turn adds all packaged dependencies to the derivation.
95 You might still see import errors in your log.
96 In this case, you will need to package the necessary dependencies yourself
97 or ask for someone else to package them.
98 If a dependency is packaged but not automatically added to this list,
99 you might need to specify it in <literal>extraPackages</literal>.
100 '';
101 };
102 };
103
104 config = mkIf cfg.enable {
105 systemd.services.home-assistant = {
106 description = "Home Assistant";
107 wantedBy = [ "multi-user.target" ];
108 after = [ "network.target" ];
109 preStart = lib.optionalString (cfg.config != null) ''
110 rm -f ${cfg.configDir}/configuration.yaml
111 ln -s ${configFile} ${cfg.configDir}/configuration.yaml
112 '';
113 serviceConfig = {
114 ExecStart = ''
115 ${package}/bin/hass --config "${cfg.configDir}"
116 '';
117 User = "hass";
118 Group = "hass";
119 Restart = "on-failure";
120 ProtectSystem = "strict";
121 ReadWritePaths = "${cfg.configDir}";
122 PrivateTmp = true;
123 };
124 };
125
126 users.extraUsers.hass = {
127 home = cfg.configDir;
128 createHome = true;
129 group = "hass";
130 uid = config.ids.uids.hass;
131 };
132
133 users.extraGroups.hass.gid = config.ids.gids.hass;
134 };
135}