416 lines
13 KiB
Nix
416 lines
13 KiB
Nix
{
|
|
config,
|
|
pkgs,
|
|
lib,
|
|
shb,
|
|
...
|
|
}:
|
|
|
|
let
|
|
cfg = config.shb.home-assistant;
|
|
|
|
fqdn = "${cfg.subdomain}.${cfg.domain}";
|
|
|
|
ldap_auth_script_repo = pkgs.fetchFromGitHub {
|
|
owner = "lldap";
|
|
repo = "lldap";
|
|
rev = "7d1f5abc137821c500de99c94f7579761fc949d8";
|
|
sha256 = "sha256-8D+7ww70Ja6Qwdfa+7MpjAAHewtCWNf/tuTAExoUrg0=";
|
|
};
|
|
|
|
ldap_auth_script = pkgs.writeShellScriptBin "ldap_auth.sh" ''
|
|
export PATH=${pkgs.gnused}/bin:${pkgs.curl}/bin:${pkgs.jq}/bin
|
|
exec ${pkgs.bash}/bin/bash ${ldap_auth_script_repo}/example_configs/lldap-ha-auth.sh $@
|
|
'';
|
|
|
|
# Filter secrets from config. Secrets are those of the form { source = <path>; }
|
|
secrets = lib.attrsets.filterAttrs (k: v: builtins.isAttrs v) cfg.config;
|
|
|
|
nonSecrets = (lib.attrsets.filterAttrs (k: v: !(builtins.isAttrs v)) cfg.config);
|
|
|
|
configWithSecretsIncludes = nonSecrets // (lib.attrsets.mapAttrs (k: v: "!secret ${k}") secrets);
|
|
in
|
|
{
|
|
imports = [
|
|
../../lib/module.nix
|
|
];
|
|
|
|
options.shb.home-assistant = {
|
|
enable = lib.mkEnableOption "selfhostblocks.home-assistant";
|
|
|
|
subdomain = lib.mkOption {
|
|
type = lib.types.str;
|
|
description = "Subdomain under which home-assistant will be served.";
|
|
example = "ha";
|
|
};
|
|
|
|
domain = lib.mkOption {
|
|
type = lib.types.str;
|
|
description = "domain under which home-assistant will be served.";
|
|
example = "mydomain.com";
|
|
};
|
|
|
|
ssl = lib.mkOption {
|
|
description = "Path to SSL files";
|
|
type = lib.types.nullOr shb.contracts.ssl.certs;
|
|
default = null;
|
|
};
|
|
|
|
config = lib.mkOption {
|
|
description = "See all available settings at https://www.home-assistant.io/docs/configuration/basic/";
|
|
type = lib.types.submodule {
|
|
freeformType = lib.types.attrsOf lib.types.str;
|
|
options = {
|
|
name = lib.mkOption {
|
|
type = lib.types.oneOf [
|
|
lib.types.str
|
|
shb.secretFileType
|
|
];
|
|
description = "Name of the Home Assistant instance.";
|
|
};
|
|
country = lib.mkOption {
|
|
type = lib.types.oneOf [
|
|
lib.types.str
|
|
shb.secretFileType
|
|
];
|
|
description = "Two letter country code where this instance is located.";
|
|
};
|
|
latitude = lib.mkOption {
|
|
type = lib.types.oneOf [
|
|
lib.types.str
|
|
shb.secretFileType
|
|
];
|
|
description = "Latitude where this instance is located.";
|
|
};
|
|
longitude = lib.mkOption {
|
|
type = lib.types.oneOf [
|
|
lib.types.str
|
|
shb.secretFileType
|
|
];
|
|
description = "Longitude where this instance is located.";
|
|
};
|
|
time_zone = lib.mkOption {
|
|
type = lib.types.oneOf [
|
|
lib.types.str
|
|
shb.secretFileType
|
|
];
|
|
description = "Timezone of this instance.";
|
|
example = "America/Los_Angeles";
|
|
};
|
|
unit_system = lib.mkOption {
|
|
type = lib.types.oneOf [
|
|
lib.types.str
|
|
(lib.types.enum [
|
|
"metric"
|
|
"us_customary"
|
|
])
|
|
];
|
|
description = "Unit system of this instance.";
|
|
example = "metric";
|
|
};
|
|
};
|
|
};
|
|
};
|
|
|
|
ldap = lib.mkOption {
|
|
description = ''
|
|
LDAP Integration App. [Manual](https://docs.nextcloud.com/server/latest/admin_manual/configuration_user/user_auth_ldap.html)
|
|
|
|
Enabling this app will create a new LDAP configuration or update one that exists with
|
|
the given host.
|
|
|
|
Also, enabling LDAP will skip onboarding
|
|
otherwise Home Assistant gets into a cyclic lock.
|
|
'';
|
|
default = { };
|
|
type = lib.types.submodule {
|
|
options = {
|
|
enable = lib.mkEnableOption "LDAP app.";
|
|
|
|
host = lib.mkOption {
|
|
type = lib.types.str;
|
|
description = ''
|
|
Host serving the LDAP server.
|
|
|
|
|
|
If set, the Home Assistant auth will be disabled. To keep it, set
|
|
`keepDefaultAuth` to `true`.
|
|
'';
|
|
default = "127.0.0.1";
|
|
};
|
|
|
|
port = lib.mkOption {
|
|
type = lib.types.port;
|
|
description = ''
|
|
Port of the service serving the LDAP server.
|
|
'';
|
|
default = 389;
|
|
};
|
|
|
|
userGroup = lib.mkOption {
|
|
type = lib.types.str;
|
|
description = "Group users must belong to to be able to login to Nextcloud.";
|
|
default = "homeassistant_user";
|
|
};
|
|
|
|
keepDefaultAuth = lib.mkOption {
|
|
type = lib.types.bool;
|
|
description = ''
|
|
Keep Home Assistant auth active, even if LDAP is configured. Usually, you want to enable
|
|
this to transfer existing users to LDAP and then you can disabled it.
|
|
'';
|
|
default = false;
|
|
};
|
|
};
|
|
};
|
|
};
|
|
|
|
voice = lib.mkOption {
|
|
description = "Options related to voice service.";
|
|
default = { };
|
|
type = lib.types.submodule {
|
|
options = {
|
|
speech-to-text = lib.mkOption {
|
|
description = ''
|
|
Wyoming piper servers.
|
|
|
|
https://search.nixos.org/options?channel=23.11&from=0&size=50&sort=relevance&type=packages&query=services.wyoming.piper.servers
|
|
'';
|
|
type = lib.types.attrsOf lib.types.anything;
|
|
default = { };
|
|
};
|
|
text-to-speech = lib.mkOption {
|
|
description = ''
|
|
Wyoming faster-whisper servers.
|
|
|
|
https://search.nixos.org/options?channel=23.11&from=0&size=50&sort=relevance&type=packages&query=services.wyoming.faster-whisper.servers
|
|
'';
|
|
type = lib.types.attrsOf lib.types.anything;
|
|
default = { };
|
|
};
|
|
wakeword = lib.mkOption {
|
|
description = ''
|
|
Wyoming open wakework servers.
|
|
|
|
https://search.nixos.org/options?channel=23.11&from=0&size=50&sort=relevance&type=packages&query=services.wyoming.openwakeword
|
|
'';
|
|
type = lib.types.anything;
|
|
default = {
|
|
enable = false;
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
|
|
backup = lib.mkOption {
|
|
description = ''
|
|
Backup configuration.
|
|
'';
|
|
default = { };
|
|
type = lib.types.submodule {
|
|
options = shb.contracts.backup.mkRequester {
|
|
user = "hass";
|
|
# No need for backup hooks as we use an hourly automation job in home assistant directly with a cron job.
|
|
sourceDirectories = [
|
|
"/var/lib/hass/backups"
|
|
];
|
|
};
|
|
};
|
|
};
|
|
|
|
dashboard = lib.mkOption {
|
|
description = ''
|
|
Dashboard contract consumer
|
|
'';
|
|
default = { };
|
|
type = lib.types.submodule {
|
|
options = shb.contracts.dashboard.mkRequester {
|
|
externalUrl = "https://${cfg.subdomain}.${cfg.domain}";
|
|
externalUrlText = "https://\${config.shb.home-assistant.subdomain}.\${config.shb.home-assistant.domain}";
|
|
internalUrl = "http://127.0.0.1:${toString config.services.home-assistant.config.http.server_port}";
|
|
internalUrlText = "http://127.0.0.1:\${config.services.home-assistant.config.http.server_port}";
|
|
};
|
|
};
|
|
};
|
|
};
|
|
|
|
config = lib.mkIf cfg.enable {
|
|
services.home-assistant = {
|
|
enable = true;
|
|
# Find them at https://github.com/NixOS/nixpkgs/blob/master/pkgs/servers/home-assistant/component-packages.nix
|
|
extraComponents = [
|
|
# Components required to complete the onboarding
|
|
"met"
|
|
"radio_browser"
|
|
];
|
|
configDir = "/var/lib/hass";
|
|
# If you can't find a component in component-packages.nix, you can add them manually with something similar to:
|
|
# extraPackages = python3Packages: [
|
|
# (python3Packages.simplisafe-python.overrideAttrs (old: rec {
|
|
# pname = "simplisafe-python";
|
|
# version = "5b003a9fa1abd00f0e9a0b99d3ee57c4c7c16bda";
|
|
# format = "pyproject";
|
|
|
|
# src = pkgs.fetchFromGitHub {
|
|
# owner = "bachya";
|
|
# repo = pname;
|
|
# rev = "${version}";
|
|
# hash = "sha256-Ij2e0QGYLjENi/yhFBQ+8qWEJp86cgwC9E27PQ5xNno=";
|
|
# };
|
|
# }))
|
|
# ];
|
|
config = {
|
|
# Includes dependencies for a basic setup
|
|
# https://www.home-assistant.io/integrations/default_config/
|
|
http = {
|
|
use_x_forwarded_for = true;
|
|
server_host = "127.0.0.1";
|
|
server_port = 8123;
|
|
trusted_proxies = "127.0.0.1";
|
|
};
|
|
logger.default = "info";
|
|
homeassistant = configWithSecretsIncludes // {
|
|
external_url = "https://${cfg.subdomain}.${cfg.domain}";
|
|
internal_url = "https://${cfg.subdomain}.${cfg.domain}";
|
|
auth_providers =
|
|
(lib.optionals (!cfg.ldap.enable || cfg.ldap.keepDefaultAuth) [
|
|
{
|
|
type = "homeassistant";
|
|
}
|
|
])
|
|
++ (lib.optionals cfg.ldap.enable [
|
|
{
|
|
type = "command_line";
|
|
command = ldap_auth_script + "/bin/ldap_auth.sh";
|
|
args = [
|
|
"http://${cfg.ldap.host}:${toString cfg.ldap.port}"
|
|
cfg.ldap.userGroup
|
|
];
|
|
meta = true;
|
|
}
|
|
]);
|
|
};
|
|
"automation ui" = "!include automations.yaml";
|
|
"scene ui" = "!include scenes.yaml";
|
|
"script ui" = "!include scripts.yaml";
|
|
|
|
"automation manual" = [
|
|
{
|
|
alias = "Create Backup on Schedule";
|
|
trigger = [
|
|
{
|
|
platform = "time_pattern";
|
|
minutes = "5";
|
|
}
|
|
];
|
|
action = [
|
|
{
|
|
service = "shell_command.delete_backups";
|
|
data = { };
|
|
}
|
|
{
|
|
service = "backup.create";
|
|
data = { };
|
|
}
|
|
];
|
|
mode = "single";
|
|
}
|
|
];
|
|
|
|
shell_command = {
|
|
delete_backups = "find ${config.services.home-assistant.configDir}/backups -type f -delete";
|
|
};
|
|
|
|
conversation.intents = {
|
|
TellJoke = [
|
|
"Tell [me] (a joke|something funny|a dad joke)"
|
|
"Raconte [moi] (une blague)"
|
|
];
|
|
};
|
|
sensor = [
|
|
{
|
|
name = "random_joke";
|
|
platform = "rest";
|
|
json_attributes = [
|
|
"joke"
|
|
"id"
|
|
"status"
|
|
];
|
|
value_template = "{{ value_json.joke }}";
|
|
resource = "https://icanhazdadjoke.com/";
|
|
scan_interval = "3600";
|
|
headers.Accept = "application/json";
|
|
}
|
|
];
|
|
intent_script.TellJoke = {
|
|
speech.text = ''{{ state_attr("sensor.random_joke", "joke") }}'';
|
|
action = {
|
|
service = "homeassistant.update_entity";
|
|
entity_id = "sensor.random_joke";
|
|
};
|
|
};
|
|
};
|
|
};
|
|
|
|
services.wyoming.piper.servers = cfg.voice.text-to-speech;
|
|
services.wyoming.faster-whisper.servers = cfg.voice.speech-to-text;
|
|
services.wyoming.openwakeword = cfg.voice.wakeword;
|
|
|
|
services.nginx.virtualHosts."${fqdn}" = {
|
|
http2 = true;
|
|
|
|
forceSSL = !(isNull cfg.ssl);
|
|
sslCertificate = lib.mkIf (!(isNull cfg.ssl)) cfg.ssl.paths.cert;
|
|
sslCertificateKey = lib.mkIf (!(isNull cfg.ssl)) cfg.ssl.paths.key;
|
|
|
|
extraConfig = ''
|
|
proxy_buffering off;
|
|
'';
|
|
locations."/" = {
|
|
proxyPass = "http://${toString config.services.home-assistant.config.http.server_host}:${toString config.services.home-assistant.config.http.server_port}/";
|
|
proxyWebsockets = true;
|
|
};
|
|
};
|
|
|
|
systemd.services.home-assistant.preStart =
|
|
(
|
|
let
|
|
# TODO: this probably does not work anymore
|
|
onboarding = pkgs.writeText "onboarding" ''
|
|
{
|
|
"version": 4,
|
|
"minor_version": 1,
|
|
"key": "onboarding",
|
|
"data": {
|
|
"done": [
|
|
${lib.optionalString cfg.ldap.enable ''"user",''}
|
|
"core_config",
|
|
"analytics"
|
|
]
|
|
}
|
|
}
|
|
'';
|
|
storage = "${config.services.home-assistant.configDir}";
|
|
file = "${storage}/.storage/onboarding";
|
|
in
|
|
''
|
|
if [ ! -f ${file} ]; then
|
|
mkdir -p ''$(dirname ${file}) && cp ${onboarding} ${file}
|
|
fi
|
|
''
|
|
)
|
|
+ (shb.replaceSecrets {
|
|
userConfig = cfg.config;
|
|
resultPath = "${config.services.home-assistant.configDir}/secrets.yaml";
|
|
generator = shb.replaceSecretsGeneratorAdapter (lib.generators.toYAML { });
|
|
});
|
|
|
|
systemd.tmpfiles.rules = [
|
|
"f ${config.services.home-assistant.configDir}/automations.yaml 0755 hass hass"
|
|
"f ${config.services.home-assistant.configDir}/scenes.yaml 0755 hass hass"
|
|
"f ${config.services.home-assistant.configDir}/scripts.yaml 0755 hass hass"
|
|
"d /var/lib/hass/backups 0750 hass hass"
|
|
];
|
|
};
|
|
}
|