2018-01-23 09:51:13 +00:00
|
|
|
|
{ config, lib, pkgs, ... }:
|
|
|
|
|
|
|
|
|
|
with lib;
|
|
|
|
|
|
|
|
|
|
let
|
|
|
|
|
cfg = config.services.home-assistant;
|
2022-01-30 01:41:15 +00:00
|
|
|
|
format = pkgs.formats.yaml {};
|
2018-01-23 09:51:13 +00:00
|
|
|
|
|
2022-01-30 01:41:15 +00:00
|
|
|
|
# Render config attribute sets to YAML
|
|
|
|
|
# Values that are null will be filtered from the output, so this is one way to have optional
|
|
|
|
|
# options shown in settings.
|
|
|
|
|
# We post-process the result to add support for YAML functions, like secrets or includes, see e.g.
|
|
|
|
|
# https://www.home-assistant.io/docs/configuration/secrets/
|
|
|
|
|
filteredConfig = lib.converge (lib.filterAttrsRecursive (_: v: ! elem v [ null ])) cfg.config or {};
|
2019-02-27 21:43:39 +00:00
|
|
|
|
configFile = pkgs.runCommand "configuration.yaml" { preferLocalBuild = true; } ''
|
2022-01-30 01:41:15 +00:00
|
|
|
|
cp ${format.generate "configuration.yaml" filteredConfig} $out
|
2020-06-02 00:48:35 +01:00
|
|
|
|
sed -i -e "s/'\!\([a-z_]\+\) \(.*\)'/\!\1 \2/;s/^\!\!/\!/;" $out
|
2019-01-24 15:49:06 +00:00
|
|
|
|
'';
|
2022-01-30 01:41:15 +00:00
|
|
|
|
lovelaceConfig = cfg.lovelaceConfig or {};
|
|
|
|
|
lovelaceConfigFile = format.generate "ui-lovelace.yaml" lovelaceConfig;
|
2019-01-24 14:52:05 +00:00
|
|
|
|
|
2022-01-30 01:41:15 +00:00
|
|
|
|
# Components advertised by the home-assistant package
|
2019-02-22 16:28:59 +00:00
|
|
|
|
availableComponents = cfg.package.availableComponents;
|
2018-02-01 12:42:07 +00:00
|
|
|
|
|
2022-01-30 01:41:15 +00:00
|
|
|
|
# Components that were added by overriding the package
|
2021-12-01 00:09:52 +00:00
|
|
|
|
explicitComponents = cfg.package.extraComponents;
|
2022-01-30 01:41:15 +00:00
|
|
|
|
useExplicitComponent = component: elem component explicitComponents;
|
2019-04-25 14:54:24 +01:00
|
|
|
|
|
|
|
|
|
# Given a component "platform", looks up whether it is used in the config
|
|
|
|
|
# as `platform = "platform";`.
|
2018-02-04 00:30:47 +00:00
|
|
|
|
#
|
2019-04-25 14:54:24 +01:00
|
|
|
|
# For example, the component mqtt.sensor is used as follows:
|
2018-02-04 00:30:47 +00:00
|
|
|
|
# config.sensor = [ {
|
2019-04-25 14:54:24 +01:00
|
|
|
|
# platform = "mqtt";
|
2018-02-04 00:30:47 +00:00
|
|
|
|
# ...
|
|
|
|
|
# } ];
|
2022-01-30 01:41:15 +00:00
|
|
|
|
usedPlatforms = config:
|
|
|
|
|
if isAttrs config then
|
|
|
|
|
optional (config ? platform) config.platform
|
|
|
|
|
++ concatMap usedPlatforms (attrValues config)
|
|
|
|
|
else if isList config then
|
|
|
|
|
concatMap usedPlatforms config
|
|
|
|
|
else [ ];
|
2018-02-04 00:30:47 +00:00
|
|
|
|
|
2022-01-30 01:41:15 +00:00
|
|
|
|
useComponentPlatform = component: elem component (usedPlatforms cfg.config);
|
2021-12-01 00:09:52 +00:00
|
|
|
|
|
2022-01-30 01:41:15 +00:00
|
|
|
|
# Returns whether component is used in config, explicitly passed into package or
|
|
|
|
|
# configured in the module.
|
2018-02-04 00:30:47 +00:00
|
|
|
|
useComponent = component:
|
|
|
|
|
hasAttrByPath (splitString "." component) cfg.config
|
2021-12-01 00:09:52 +00:00
|
|
|
|
|| useComponentPlatform component
|
2022-01-30 01:35:34 +00:00
|
|
|
|
|| useExplicitComponent component
|
|
|
|
|
|| builtins.elem component cfg.extraComponents;
|
2018-02-01 12:42:07 +00:00
|
|
|
|
|
2022-01-30 01:41:15 +00:00
|
|
|
|
# Final list of components passed into the package to include required dependencies
|
2018-02-01 12:42:07 +00:00
|
|
|
|
extraComponents = filter useComponent availableComponents;
|
|
|
|
|
|
2022-01-30 01:35:34 +00:00
|
|
|
|
package = (cfg.package.override (oldArgs: {
|
|
|
|
|
# Respect overrides that already exist in the passed package and
|
|
|
|
|
# concat it with values passed via the module.
|
2022-02-16 23:45:38 +00:00
|
|
|
|
extraComponents = oldArgs.extraComponents or [] ++ extraComponents;
|
|
|
|
|
extraPackages = ps: (oldArgs.extraPackages or (_: []) ps) ++ (cfg.extraPackages ps);
|
2022-01-30 01:35:34 +00:00
|
|
|
|
}));
|
2018-01-23 09:51:13 +00:00
|
|
|
|
in {
|
2022-01-30 01:41:15 +00:00
|
|
|
|
imports = [
|
|
|
|
|
# Migrations in NixOS 22.05
|
|
|
|
|
(mkRemovedOptionModule [ "services" "home-assistant" "applyDefaultConfig" ] "The default config was migrated into services.home-assistant.config")
|
|
|
|
|
(mkRemovedOptionModule [ "services" "home-assistant" "autoExtraComponents" ] "Components are now parsed from services.home-assistant.config unconditionally")
|
|
|
|
|
(mkRenamedOptionModule [ "services" "home-assistant" "port" ] [ "services" "home-assistant" "config" "http" "server_port" ])
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
meta = {
|
|
|
|
|
buildDocsInSandbox = false;
|
|
|
|
|
maintainers = teams.home-assistant.members;
|
|
|
|
|
};
|
2018-01-23 09:51:13 +00:00
|
|
|
|
|
|
|
|
|
options.services.home-assistant = {
|
2021-06-14 23:23:49 +01:00
|
|
|
|
# Running home-assistant on NixOS is considered an installation method that is unsupported by the upstream project.
|
|
|
|
|
# https://github.com/home-assistant/architecture/blob/master/adr/0012-define-supported-installation-method.md#decision
|
|
|
|
|
enable = mkEnableOption "Home Assistant. Please note that this installation method is unsupported upstream";
|
2018-01-23 09:51:13 +00:00
|
|
|
|
|
|
|
|
|
configDir = mkOption {
|
|
|
|
|
default = "/var/lib/hass";
|
|
|
|
|
type = types.path;
|
|
|
|
|
description = "The config directory, where your <filename>configuration.yaml</filename> is located.";
|
|
|
|
|
};
|
|
|
|
|
|
2022-01-30 01:35:34 +00:00
|
|
|
|
extraComponents = mkOption {
|
|
|
|
|
type = types.listOf (types.enum availableComponents);
|
|
|
|
|
default = [
|
|
|
|
|
# List of components required to complete the onboarding
|
|
|
|
|
"default_config"
|
|
|
|
|
"met"
|
|
|
|
|
"esphome"
|
2022-01-30 17:07:25 +00:00
|
|
|
|
] ++ optionals (pkgs.stdenv.hostPlatform.isAarch32 || pkgs.stdenv.hostPlatform.isAarch64) [
|
|
|
|
|
# Use the platform as an indicator that we might be running on a RaspberryPi and include
|
|
|
|
|
# relevant components
|
|
|
|
|
"rpi_power"
|
2022-01-30 01:35:34 +00:00
|
|
|
|
];
|
|
|
|
|
example = literalExpression ''
|
|
|
|
|
[
|
|
|
|
|
"analytics"
|
|
|
|
|
"default_config"
|
|
|
|
|
"esphome"
|
|
|
|
|
"my"
|
|
|
|
|
"shopping_list"
|
|
|
|
|
"wled"
|
|
|
|
|
]
|
|
|
|
|
'';
|
|
|
|
|
description = ''
|
|
|
|
|
List of <link xlink:href="https://www.home-assistant.io/integrations/">components</link> that have their dependencies included in the package.
|
|
|
|
|
|
|
|
|
|
The component name can be found in the URL, for example <literal>https://www.home-assistant.io/integrations/ffmpeg/</literal> would map to <literal>ffmpeg</literal>.
|
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
|
2022-01-30 02:46:41 +00:00
|
|
|
|
extraPackages = mkOption {
|
|
|
|
|
type = types.functionTo (types.listOf types.package);
|
|
|
|
|
default = _: [];
|
|
|
|
|
defaultText = literalExpression ''
|
|
|
|
|
python3Packages: with python3Packages; [];
|
|
|
|
|
'';
|
|
|
|
|
example = literalExpression ''
|
|
|
|
|
python3Packages: with python3Packages; [
|
|
|
|
|
# postgresql support
|
|
|
|
|
psycopg2
|
|
|
|
|
];
|
|
|
|
|
'';
|
|
|
|
|
description = ''
|
|
|
|
|
List of packages to add to propagatedBuildInputs.
|
|
|
|
|
|
|
|
|
|
A popular example is <package>python3Packages.psycopg2</package>
|
|
|
|
|
for PostgreSQL support in the recorder component.
|
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
|
2022-01-30 01:41:15 +00:00
|
|
|
|
config = mkOption {
|
|
|
|
|
type = types.submodule {
|
|
|
|
|
freeformType = format.type;
|
|
|
|
|
options = {
|
|
|
|
|
# This is a partial selection of the most common options, so new users can quickly
|
|
|
|
|
# pick up how to match home-assistants config structure to ours. It also lets us preset
|
|
|
|
|
# config values intelligently.
|
2018-04-21 15:32:09 +01:00
|
|
|
|
|
2022-01-30 01:41:15 +00:00
|
|
|
|
homeassistant = {
|
|
|
|
|
# https://www.home-assistant.io/docs/configuration/basic/
|
|
|
|
|
name = mkOption {
|
|
|
|
|
type = types.nullOr types.str;
|
|
|
|
|
default = null;
|
|
|
|
|
example = "Home";
|
|
|
|
|
description = ''
|
|
|
|
|
Name of the location where Home Assistant is running.
|
|
|
|
|
'';
|
|
|
|
|
};
|
2018-04-21 15:32:09 +01:00
|
|
|
|
|
2022-01-30 01:41:15 +00:00
|
|
|
|
latitude = mkOption {
|
|
|
|
|
type = types.nullOr (types.either types.float types.str);
|
|
|
|
|
default = null;
|
|
|
|
|
example = 52.3;
|
|
|
|
|
description = ''
|
|
|
|
|
Latitude of your location required to calculate the time the sun rises and sets.
|
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
longitude = mkOption {
|
|
|
|
|
type = types.nullOr (types.either types.float types.str);
|
|
|
|
|
default = null;
|
|
|
|
|
example = 4.9;
|
|
|
|
|
description = ''
|
|
|
|
|
Longitude of your location required to calculate the time the sun rises and sets.
|
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
unit_system = mkOption {
|
|
|
|
|
type = types.nullOr (types.enum [ "metric" "imperial" ]);
|
|
|
|
|
default = null;
|
|
|
|
|
example = "metric";
|
|
|
|
|
description = ''
|
|
|
|
|
The unit system to use. This also sets temperature_unit, Celsius for Metric and Fahrenheit for Imperial.
|
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
temperature_unit = mkOption {
|
|
|
|
|
type = types.nullOr (types.enum [ "C" "F" ]);
|
|
|
|
|
default = null;
|
|
|
|
|
example = "C";
|
|
|
|
|
description = ''
|
|
|
|
|
Override temperature unit set by unit_system. <literal>C</literal> for Celsius, <literal>F</literal> for Fahrenheit.
|
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
time_zone = mkOption {
|
|
|
|
|
type = types.nullOr types.str;
|
|
|
|
|
default = config.time.timeZone or null;
|
|
|
|
|
defaultText = literalExpression ''
|
|
|
|
|
config.time.timeZone or null
|
|
|
|
|
'';
|
|
|
|
|
example = "Europe/Amsterdam";
|
|
|
|
|
description = ''
|
|
|
|
|
Pick your time zone from the column TZ of Wikipedia’s <link xlink:href="https://en.wikipedia.org/wiki/List_of_tz_database_time_zones">list of tz database time zones</link>.
|
|
|
|
|
'';
|
|
|
|
|
};
|
2020-01-28 23:09:52 +00:00
|
|
|
|
};
|
2022-01-30 01:41:15 +00:00
|
|
|
|
|
|
|
|
|
http = {
|
|
|
|
|
# https://www.home-assistant.io/integrations/http/
|
|
|
|
|
server_host = mkOption {
|
|
|
|
|
type = types.either types.str (types.listOf types.str);
|
|
|
|
|
default = [
|
|
|
|
|
"0.0.0.0"
|
|
|
|
|
"::"
|
|
|
|
|
];
|
|
|
|
|
example = "::1";
|
|
|
|
|
description = ''
|
|
|
|
|
Only listen to incoming requests on specific IP/host. The default listed assumes support for IPv4 and IPv6.
|
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
server_port = mkOption {
|
|
|
|
|
default = 8123;
|
|
|
|
|
type = types.port;
|
|
|
|
|
description = ''
|
|
|
|
|
The port on which to listen.
|
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
lovelace = {
|
|
|
|
|
# https://www.home-assistant.io/lovelace/dashboards/
|
|
|
|
|
mode = mkOption {
|
|
|
|
|
type = types.enum [ "yaml" "storage" ];
|
|
|
|
|
default = if cfg.lovelaceConfig != null
|
|
|
|
|
then "yaml"
|
|
|
|
|
else "storage";
|
|
|
|
|
defaultText = literalExpression ''
|
|
|
|
|
if cfg.lovelaceConfig != null
|
|
|
|
|
then "yaml"
|
|
|
|
|
else "storage";
|
|
|
|
|
'';
|
|
|
|
|
example = "yaml";
|
|
|
|
|
description = ''
|
|
|
|
|
In what mode should the main Lovelace panel be, <literal>yaml</literal> or <literal>storage</literal> (UI managed).
|
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
};
|
2021-10-03 17:06:03 +01:00
|
|
|
|
example = literalExpression ''
|
2018-01-23 09:51:13 +00:00
|
|
|
|
{
|
|
|
|
|
homeassistant = {
|
|
|
|
|
name = "Home";
|
2019-12-20 08:40:16 +00:00
|
|
|
|
latitude = "!secret latitude";
|
|
|
|
|
longitude = "!secret longitude";
|
|
|
|
|
elevation = "!secret elevation";
|
|
|
|
|
unit_system = "metric";
|
2018-01-23 09:51:13 +00:00
|
|
|
|
time_zone = "UTC";
|
|
|
|
|
};
|
2020-06-02 00:48:35 +01:00
|
|
|
|
frontend = {
|
|
|
|
|
themes = "!include_dir_merge_named themes";
|
|
|
|
|
};
|
2022-01-30 01:41:15 +00:00
|
|
|
|
http = {};
|
2018-02-01 12:42:07 +00:00
|
|
|
|
feedreader.urls = [ "https://nixos.org/blogs.xml" ];
|
2018-01-23 09:51:13 +00:00
|
|
|
|
}
|
|
|
|
|
'';
|
|
|
|
|
description = ''
|
|
|
|
|
Your <filename>configuration.yaml</filename> as a Nix attribute set.
|
2022-01-30 01:41:15 +00:00
|
|
|
|
|
|
|
|
|
YAML functions like <link xlink:href="https://www.home-assistant.io/docs/configuration/secrets/">secrets</link>
|
|
|
|
|
can be passed as a string and will be unquoted automatically.
|
|
|
|
|
|
|
|
|
|
Unless this option is explicitly set to <literal>null</literal>
|
|
|
|
|
we assume your <filename>configuration.yaml</filename> is
|
|
|
|
|
managed through this module and thereby overwritten on startup.
|
2018-01-23 09:51:13 +00:00
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
|
2019-02-03 11:45:52 +00:00
|
|
|
|
configWritable = mkOption {
|
|
|
|
|
default = false;
|
|
|
|
|
type = types.bool;
|
|
|
|
|
description = ''
|
|
|
|
|
Whether to make <filename>configuration.yaml</filename> writable.
|
2022-01-30 01:41:15 +00:00
|
|
|
|
|
2019-02-03 11:45:52 +00:00
|
|
|
|
This will allow you to edit it from Home Assistant's web interface.
|
2022-01-30 01:41:15 +00:00
|
|
|
|
|
|
|
|
|
This only has an effect if <option>config</option> is set.
|
2019-02-03 11:45:52 +00:00
|
|
|
|
However, bear in mind that it will be overwritten at every start of the service.
|
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
|
2019-01-24 14:52:05 +00:00
|
|
|
|
lovelaceConfig = mkOption {
|
|
|
|
|
default = null;
|
2022-01-30 01:41:15 +00:00
|
|
|
|
type = types.nullOr format.type;
|
|
|
|
|
# from https://www.home-assistant.io/lovelace/dashboards/
|
2021-10-03 17:06:03 +01:00
|
|
|
|
example = literalExpression ''
|
2019-01-24 14:52:05 +00:00
|
|
|
|
{
|
|
|
|
|
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.
|
2022-01-30 01:41:15 +00:00
|
|
|
|
Setting this option will automatically set <literal>lovelace.mode</literal> to <literal>yaml</literal>.
|
|
|
|
|
|
2019-01-24 14:52:05 +00:00
|
|
|
|
Beware that setting this option will delete your previous <filename>ui-lovelace.yaml</filename>
|
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
|
2019-02-03 11:45:52 +00:00
|
|
|
|
lovelaceConfigWritable = mkOption {
|
|
|
|
|
default = false;
|
|
|
|
|
type = types.bool;
|
|
|
|
|
description = ''
|
|
|
|
|
Whether to make <filename>ui-lovelace.yaml</filename> writable.
|
2022-01-30 01:41:15 +00:00
|
|
|
|
|
2019-02-03 11:45:52 +00:00
|
|
|
|
This will allow you to edit it from Home Assistant's web interface.
|
2022-01-30 01:41:15 +00:00
|
|
|
|
|
|
|
|
|
This only has an effect if <option>lovelaceConfig</option> is set.
|
2019-02-03 11:45:52 +00:00
|
|
|
|
However, bear in mind that it will be overwritten at every start of the service.
|
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
|
2018-01-23 09:51:13 +00:00
|
|
|
|
package = mkOption {
|
2021-04-13 21:46:36 +01:00
|
|
|
|
default = pkgs.home-assistant.overrideAttrs (oldAttrs: {
|
|
|
|
|
doInstallCheck = false;
|
2021-04-11 23:00:57 +01:00
|
|
|
|
});
|
2021-10-03 17:06:03 +01:00
|
|
|
|
defaultText = literalExpression ''
|
2021-04-13 21:46:36 +01:00
|
|
|
|
pkgs.home-assistant.overrideAttrs (oldAttrs: {
|
|
|
|
|
doInstallCheck = false;
|
2021-04-11 23:00:57 +01:00
|
|
|
|
})
|
2021-03-06 01:47:39 +00:00
|
|
|
|
'';
|
2018-01-23 09:51:13 +00:00
|
|
|
|
type = types.package;
|
2021-10-03 17:06:03 +01:00
|
|
|
|
example = literalExpression ''
|
2018-01-23 09:51:13 +00:00
|
|
|
|
pkgs.home-assistant.override {
|
2022-01-28 21:55:27 +00:00
|
|
|
|
extraPackages = python3Packages: with python3Packages; [
|
|
|
|
|
psycopg2
|
|
|
|
|
];
|
|
|
|
|
extraComponents = [
|
|
|
|
|
"default_config"
|
|
|
|
|
"esphome"
|
|
|
|
|
"met"
|
|
|
|
|
];
|
2018-01-23 09:51:13 +00:00
|
|
|
|
}
|
|
|
|
|
'';
|
|
|
|
|
description = ''
|
2022-01-30 01:41:15 +00:00
|
|
|
|
The Home Assistant package to use.
|
2018-07-20 18:28:29 +01:00
|
|
|
|
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.
|
2021-04-17 01:20:07 +01:00
|
|
|
|
Avoid <literal>home-assistant.overridePythonAttrs</literal> if you use <literal>autoExtraComponents</literal>.
|
2018-02-01 12:42:07 +00:00
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
|
2018-05-22 02:14:04 +01:00
|
|
|
|
openFirewall = mkOption {
|
|
|
|
|
default = false;
|
|
|
|
|
type = types.bool;
|
|
|
|
|
description = "Whether to open the firewall for the specified port.";
|
|
|
|
|
};
|
2018-01-23 09:51:13 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
config = mkIf cfg.enable {
|
2018-05-22 02:14:04 +01:00
|
|
|
|
networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [ cfg.port ];
|
|
|
|
|
|
2018-01-23 09:51:13 +00:00
|
|
|
|
systemd.services.home-assistant = {
|
|
|
|
|
description = "Home Assistant";
|
2022-02-01 20:25:12 +00:00
|
|
|
|
after = [
|
|
|
|
|
"network-online.target"
|
|
|
|
|
|
|
|
|
|
# prevent races with database creation
|
|
|
|
|
"mysql.service"
|
|
|
|
|
"postgresql.service"
|
|
|
|
|
];
|
2022-02-12 18:47:47 +00:00
|
|
|
|
preStart = let
|
|
|
|
|
copyConfig = 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"
|
|
|
|
|
'';
|
|
|
|
|
copyLovelaceConfig = 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"
|
|
|
|
|
'';
|
|
|
|
|
in
|
|
|
|
|
(optionalString (cfg.config != null) copyConfig) +
|
|
|
|
|
(optionalString (cfg.lovelaceConfig != null) copyLovelaceConfig)
|
|
|
|
|
;
|
2021-04-24 13:52:14 +01:00
|
|
|
|
serviceConfig = let
|
|
|
|
|
# List of capabilities to equip home-assistant with, depending on configured components
|
|
|
|
|
capabilities = [
|
|
|
|
|
# Empty string first, so we will never accidentally have an empty capability bounding set
|
|
|
|
|
# https://github.com/NixOS/nixpkgs/issues/120617#issuecomment-830685115
|
|
|
|
|
""
|
|
|
|
|
] ++ (unique (optionals (useComponent "bluetooth_tracker" || useComponent "bluetooth_le_tracker") [
|
|
|
|
|
# Required for interaction with hci devices and bluetooth sockets
|
|
|
|
|
# https://www.home-assistant.io/integrations/bluetooth_le_tracker/#rootless-setup-on-core-installs
|
|
|
|
|
"CAP_NET_ADMIN"
|
|
|
|
|
"CAP_NET_RAW"
|
|
|
|
|
] ++ lib.optionals (useComponent "emulated_hue") [
|
|
|
|
|
# Alexa looks for the service on port 80
|
|
|
|
|
# https://www.home-assistant.io/integrations/emulated_hue
|
|
|
|
|
"CAP_NET_BIND_SERVICE"
|
|
|
|
|
] ++ lib.optionals (useComponent "nmap_tracker") [
|
|
|
|
|
# https://www.home-assistant.io/integrations/nmap_tracker#linux-capabilities
|
|
|
|
|
"CAP_NET_ADMIN"
|
|
|
|
|
"CAP_NET_BIND_SERVICE"
|
|
|
|
|
"CAP_NET_RAW"
|
|
|
|
|
]));
|
2021-06-16 20:31:24 +01:00
|
|
|
|
componentsUsingBluetooth = [
|
|
|
|
|
# Components that require the AF_BLUETOOTH address family
|
|
|
|
|
"bluetooth_tracker"
|
|
|
|
|
"bluetooth_le_tracker"
|
|
|
|
|
];
|
2022-01-25 17:29:16 +00:00
|
|
|
|
componentsUsingPing = [
|
|
|
|
|
# Components that require the capset syscall for the ping wrapper
|
|
|
|
|
"ping"
|
|
|
|
|
"wake_on_lan"
|
|
|
|
|
];
|
2021-06-16 20:31:24 +01:00
|
|
|
|
componentsUsingSerialDevices = [
|
|
|
|
|
# Components that require access to serial devices (/dev/tty*)
|
|
|
|
|
# List generated from home-assistant documentation:
|
|
|
|
|
# git clone https://github.com/home-assistant/home-assistant.io/
|
|
|
|
|
# cd source/_integrations
|
|
|
|
|
# rg "/dev/tty" -l | cut -d'/' -f3 | cut -d'.' -f1 | sort
|
|
|
|
|
# And then extended by references found in the source code, these
|
|
|
|
|
# mostly the ones using config flows already.
|
|
|
|
|
"acer_projector"
|
|
|
|
|
"alarmdecoder"
|
|
|
|
|
"arduino"
|
|
|
|
|
"blackbird"
|
2021-08-24 11:09:12 +01:00
|
|
|
|
"deconz"
|
2021-06-16 20:31:24 +01:00
|
|
|
|
"dsmr"
|
|
|
|
|
"edl21"
|
|
|
|
|
"elkm1"
|
|
|
|
|
"elv"
|
|
|
|
|
"enocean"
|
|
|
|
|
"firmata"
|
|
|
|
|
"flexit"
|
|
|
|
|
"gpsd"
|
|
|
|
|
"insteon"
|
|
|
|
|
"kwb"
|
|
|
|
|
"lacrosse"
|
|
|
|
|
"mhz19"
|
|
|
|
|
"modbus"
|
|
|
|
|
"modem_callerid"
|
|
|
|
|
"mysensors"
|
|
|
|
|
"nad"
|
|
|
|
|
"numato"
|
|
|
|
|
"rflink"
|
|
|
|
|
"rfxtrx"
|
|
|
|
|
"scsgate"
|
|
|
|
|
"serial"
|
|
|
|
|
"serial_pm"
|
|
|
|
|
"sms"
|
|
|
|
|
"upb"
|
2021-09-15 17:18:49 +01:00
|
|
|
|
"usb"
|
2021-06-16 20:31:24 +01:00
|
|
|
|
"velbus"
|
|
|
|
|
"w800rf32"
|
|
|
|
|
"xbee"
|
|
|
|
|
"zha"
|
2021-07-08 12:27:03 +01:00
|
|
|
|
"zwave"
|
2021-09-15 17:18:49 +01:00
|
|
|
|
"zwave_js"
|
2021-06-16 20:31:24 +01:00
|
|
|
|
];
|
2021-04-24 13:52:14 +01:00
|
|
|
|
in {
|
2022-02-03 00:37:20 +00:00
|
|
|
|
ExecStart = "${package}/bin/hass --config '${cfg.configDir}'";
|
2020-05-31 09:22:22 +01:00
|
|
|
|
ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
|
2018-01-23 09:51:13 +00:00
|
|
|
|
User = "hass";
|
|
|
|
|
Group = "hass";
|
|
|
|
|
Restart = "on-failure";
|
2021-04-26 01:39:09 +01:00
|
|
|
|
RestartForceExitStatus = "100";
|
|
|
|
|
SuccessExitStatus = "100";
|
2021-04-24 13:52:14 +01:00
|
|
|
|
KillSignal = "SIGINT";
|
|
|
|
|
|
|
|
|
|
# Hardening
|
|
|
|
|
AmbientCapabilities = capabilities;
|
|
|
|
|
CapabilityBoundingSet = capabilities;
|
2021-06-16 20:31:24 +01:00
|
|
|
|
DeviceAllow = (optionals (any useComponent componentsUsingSerialDevices) [
|
2021-04-24 13:52:14 +01:00
|
|
|
|
"char-ttyACM rw"
|
|
|
|
|
"char-ttyAMA rw"
|
|
|
|
|
"char-ttyUSB rw"
|
2021-06-16 20:31:24 +01:00
|
|
|
|
]);
|
2021-04-24 13:52:14 +01:00
|
|
|
|
DevicePolicy = "closed";
|
|
|
|
|
LockPersonality = true;
|
|
|
|
|
MemoryDenyWriteExecute = true;
|
|
|
|
|
NoNewPrivileges = true;
|
|
|
|
|
PrivateTmp = true;
|
|
|
|
|
PrivateUsers = false; # prevents gaining capabilities in the host namespace
|
|
|
|
|
ProtectClock = true;
|
|
|
|
|
ProtectControlGroups = true;
|
|
|
|
|
ProtectHome = true;
|
|
|
|
|
ProtectHostname = true;
|
|
|
|
|
ProtectKernelLogs = true;
|
|
|
|
|
ProtectKernelModules = true;
|
|
|
|
|
ProtectKernelTunables = true;
|
|
|
|
|
ProtectProc = "invisible";
|
2021-05-06 15:44:16 +01:00
|
|
|
|
ProcSubset = "all";
|
2018-01-23 09:51:13 +00:00
|
|
|
|
ProtectSystem = "strict";
|
2021-04-24 13:52:14 +01:00
|
|
|
|
RemoveIPC = true;
|
2020-11-19 12:29:03 +00:00
|
|
|
|
ReadWritePaths = let
|
2021-04-24 13:52:14 +01:00
|
|
|
|
# Allow rw access to explicitly configured paths
|
2020-11-19 12:29:03 +00:00
|
|
|
|
cfgPath = [ "config" "homeassistant" "allowlist_external_dirs" ];
|
|
|
|
|
value = attrByPath cfgPath [] cfg;
|
|
|
|
|
allowPaths = if isList value then value else singleton value;
|
|
|
|
|
in [ "${cfg.configDir}" ] ++ allowPaths;
|
2021-04-24 13:52:14 +01:00
|
|
|
|
RestrictAddressFamilies = [
|
|
|
|
|
"AF_INET"
|
|
|
|
|
"AF_INET6"
|
2021-05-06 15:44:16 +01:00
|
|
|
|
"AF_NETLINK"
|
|
|
|
|
"AF_UNIX"
|
2021-06-16 20:31:24 +01:00
|
|
|
|
] ++ optionals (any useComponent componentsUsingBluetooth) [
|
2021-04-24 13:52:14 +01:00
|
|
|
|
"AF_BLUETOOTH"
|
|
|
|
|
];
|
|
|
|
|
RestrictNamespaces = true;
|
|
|
|
|
RestrictRealtime = true;
|
|
|
|
|
RestrictSUIDSGID = true;
|
2021-06-16 20:31:24 +01:00
|
|
|
|
SupplementaryGroups = optionals (any useComponent componentsUsingSerialDevices) [
|
|
|
|
|
"dialout"
|
|
|
|
|
];
|
2021-04-24 13:52:14 +01:00
|
|
|
|
SystemCallArchitectures = "native";
|
|
|
|
|
SystemCallFilter = [
|
|
|
|
|
"@system-service"
|
|
|
|
|
"~@privileged"
|
2022-01-25 17:29:16 +00:00
|
|
|
|
] ++ optionals (any useComponent componentsUsingPing) [
|
|
|
|
|
"capset"
|
2021-04-24 13:52:14 +01:00
|
|
|
|
];
|
|
|
|
|
UMask = "0077";
|
2018-01-23 09:51:13 +00:00
|
|
|
|
};
|
2018-03-18 11:46:36 +00:00
|
|
|
|
path = [
|
|
|
|
|
"/run/wrappers" # needed for ping
|
|
|
|
|
];
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
systemd.targets.home-assistant = rec {
|
|
|
|
|
description = "Home Assistant";
|
|
|
|
|
wantedBy = [ "multi-user.target" ];
|
|
|
|
|
wants = [ "home-assistant.service" ];
|
|
|
|
|
after = wants;
|
2018-01-23 09:51:13 +00:00
|
|
|
|
};
|
|
|
|
|
|
2018-06-30 00:58:35 +01:00
|
|
|
|
users.users.hass = {
|
2018-01-23 09:51:13 +00:00
|
|
|
|
home = cfg.configDir;
|
|
|
|
|
createHome = true;
|
|
|
|
|
group = "hass";
|
|
|
|
|
uid = config.ids.uids.hass;
|
|
|
|
|
};
|
|
|
|
|
|
2018-06-30 00:58:35 +01:00
|
|
|
|
users.groups.hass.gid = config.ids.gids.hass;
|
2018-01-23 09:51:13 +00:00
|
|
|
|
};
|
|
|
|
|
}
|