mirror of
https://github.com/oddlama/nix-config.git
synced 2025-10-10 23:00:39 +02:00
feat: configure homeassistant and esphome on new machine
This commit is contained in:
parent
0ff0828ca9
commit
3d37e2959f
52 changed files with 403 additions and 672 deletions
|
@ -19,6 +19,10 @@
|
|||
|
||||
./fs.nix
|
||||
./net.nix
|
||||
|
||||
./esphome.nix
|
||||
./home-assistant.nix
|
||||
./mosquitto.nix
|
||||
];
|
||||
|
||||
topology.self.hardware.info = "Intel N100, 16GB RAM";
|
||||
|
|
59
hosts/sausebiene/esphome.nix
Normal file
59
hosts/sausebiene/esphome.nix
Normal file
|
@ -0,0 +1,59 @@
|
|||
{
|
||||
config,
|
||||
globals,
|
||||
...
|
||||
}:
|
||||
let
|
||||
esphomeDomain = "esphome.${globals.domains.personal}";
|
||||
in
|
||||
{
|
||||
wireguard.proxy-home.firewallRuleForNode.ward-web-proxy.allowedTCPPorts = [
|
||||
config.services.esphome.port
|
||||
];
|
||||
|
||||
environment.persistence."/persist".directories = [
|
||||
{
|
||||
directory = "/var/lib/private/esphome";
|
||||
mode = "0700";
|
||||
}
|
||||
];
|
||||
|
||||
globals.services.esphome.domain = esphomeDomain;
|
||||
# globals.monitoring.http.esphome = {
|
||||
# url = "https://${esphomeDomain}";
|
||||
# expectedBodyRegex = "esphome";
|
||||
# network = "internet";
|
||||
# };
|
||||
|
||||
topology.self.services.esphome.info = "https://${esphomeDomain}";
|
||||
services.esphome = {
|
||||
enable = true;
|
||||
address = "0.0.0.0";
|
||||
port = 3001;
|
||||
};
|
||||
|
||||
nodes.ward-web-proxy = {
|
||||
services.nginx = {
|
||||
upstreams."esphome" = {
|
||||
servers."${config.wireguard.proxy-home.ipv4}:${toString config.services.esphome.port}" = { };
|
||||
extraConfig = ''
|
||||
zone esphome 64k;
|
||||
keepalive 2;
|
||||
'';
|
||||
};
|
||||
virtualHosts.${esphomeDomain} = {
|
||||
forceSSL = true;
|
||||
useACMEWildcardHost = true;
|
||||
locations."/" = {
|
||||
proxyPass = "http://esphome";
|
||||
proxyWebsockets = true;
|
||||
};
|
||||
extraConfig = ''
|
||||
allow ${globals.net.home-lan.vlans.home.cidrv4};
|
||||
allow ${globals.net.home-lan.vlans.home.cidrv6};
|
||||
deny all;
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
192
hosts/sausebiene/home-assistant.nix
Normal file
192
hosts/sausebiene/home-assistant.nix
Normal file
|
@ -0,0 +1,192 @@
|
|||
{
|
||||
config,
|
||||
globals,
|
||||
lib,
|
||||
nodes,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
homeassistantDomain = "home.${globals.domains.personal}";
|
||||
fritzboxDomain = "fritzbox.${globals.domains.me}";
|
||||
in
|
||||
{
|
||||
wireguard.proxy-home.firewallRuleForNode.ward-web-proxy.allowedTCPPorts = [
|
||||
config.services.home-assistant.config.http.server_port
|
||||
];
|
||||
|
||||
environment.persistence."/persist".directories = [
|
||||
{
|
||||
directory = config.services.home-assistant.configDir;
|
||||
user = "hass";
|
||||
group = "hass";
|
||||
mode = "0700";
|
||||
}
|
||||
];
|
||||
|
||||
globals.services.home-assistant.domain = homeassistantDomain;
|
||||
# globals.monitoring.http.homeassistant = {
|
||||
# url = "https://${homeasisstantDomain}";
|
||||
# expectedBodyRegex = "homeassistant";
|
||||
# network = "internet";
|
||||
# };
|
||||
|
||||
topology.self.services.home-assistant.info = "https://${homeassistantDomain}";
|
||||
services.home-assistant = {
|
||||
enable = true;
|
||||
extraComponents = [
|
||||
"radio_browser"
|
||||
"met"
|
||||
"esphome"
|
||||
"fritzbox"
|
||||
"soundtouch"
|
||||
"spotify"
|
||||
"matter"
|
||||
#"zha"
|
||||
"mqtt"
|
||||
"ollama"
|
||||
];
|
||||
|
||||
customLovelaceModules =
|
||||
let
|
||||
mods = pkgs.home-assistant-custom-lovelace-modules;
|
||||
in
|
||||
[
|
||||
mods.bubble-card
|
||||
mods.weather-card
|
||||
mods.mini-graph-card
|
||||
mods.card-mod
|
||||
mods.mushroom
|
||||
mods.multiple-entity-row
|
||||
mods.button-card
|
||||
mods.weather-chart-card
|
||||
mods.hourly-weather
|
||||
];
|
||||
|
||||
config = {
|
||||
default_config = { };
|
||||
http = {
|
||||
server_host = [ "0.0.0.0" ];
|
||||
server_port = 8123;
|
||||
use_x_forwarded_for = true;
|
||||
trusted_proxies = [ nodes.ward-web-proxy.config.wireguard.proxy-home.ipv4 ];
|
||||
};
|
||||
|
||||
homeassistant = {
|
||||
name = "!secret ha_name";
|
||||
latitude = "!secret ha_latitude";
|
||||
longitude = "!secret ha_longitude";
|
||||
elevation = "!secret ha_elevation";
|
||||
currency = "EUR";
|
||||
time_zone = "Europe/Berlin";
|
||||
unit_system = "metric";
|
||||
#external_url = "https://";
|
||||
packages.manual = "!include manual.yaml";
|
||||
};
|
||||
|
||||
lovelace.mode = "yaml";
|
||||
|
||||
frontend = {
|
||||
themes = "!include_dir_merge_named themes";
|
||||
};
|
||||
"automation ui" = "!include automations.yaml";
|
||||
|
||||
# influxdb = {
|
||||
# api_version = 2;
|
||||
# host = globals.services.influxdb.domain;
|
||||
# port = "443";
|
||||
# max_retries = 10;
|
||||
# ssl = true;
|
||||
# verify_ssl = true;
|
||||
# token = "!secret influxdb_token";
|
||||
# organization = "home";
|
||||
# bucket = "home_assistant";
|
||||
# };
|
||||
|
||||
};
|
||||
extraPackages =
|
||||
python3Packages: with python3Packages; [
|
||||
psycopg2
|
||||
gtts
|
||||
fritzconnection
|
||||
adguardhome
|
||||
zlib-ng
|
||||
pymodbus
|
||||
];
|
||||
};
|
||||
|
||||
age.secrets."home-assistant-secrets.yaml" = {
|
||||
rekeyFile = ./secrets/home-assistant-secrets.yaml.age;
|
||||
owner = "hass";
|
||||
};
|
||||
|
||||
systemd.services.home-assistant = {
|
||||
preStart = lib.mkBefore ''
|
||||
if [[ -e ${config.services.home-assistant.configDir}/secrets.yaml ]]; then
|
||||
rm ${config.services.home-assistant.configDir}/secrets.yaml
|
||||
fi
|
||||
|
||||
# Update influxdb token
|
||||
# We don't use -i because it would require chown with is a @privileged syscall
|
||||
INFLUXDB_TOKEN="$(cat ${config.age.secrets.hass-influxdb-token.path})" \
|
||||
${lib.getExe pkgs.yq-go} '.influxdb_token = strenv(INFLUXDB_TOKEN)' \
|
||||
${
|
||||
config.age.secrets."home-assistant-secrets.yaml".path
|
||||
} > ${config.services.home-assistant.configDir}/secrets.yaml
|
||||
|
||||
touch -a ${config.services.home-assistant.configDir}/{automations,scenes,scripts,manual}.yaml
|
||||
'';
|
||||
};
|
||||
|
||||
age.secrets.hass-influxdb-token = {
|
||||
generator.script = "alnum";
|
||||
mode = "440";
|
||||
group = "hass";
|
||||
};
|
||||
|
||||
# nodes.sire-influxdb = {
|
||||
# # Mirror the original secret on the influx host
|
||||
# age.secrets."hass-influxdb-token-${config.node.name}" = {
|
||||
# inherit (config.age.secrets.hass-influxdb-token) rekeyFile;
|
||||
# mode = "440";
|
||||
# group = "influxdb2";
|
||||
# };
|
||||
#
|
||||
# services.influxdb2.provision.organizations.home.auths."home-assistant (${config.node.name})" = {
|
||||
# readBuckets = [ "home_assistant" ];
|
||||
# writeBuckets = [ "home_assistant" ];
|
||||
# tokenFile = nodes.sire-influxdb.config.age.secrets."hass-influxdb-token-${config.node.name}".path;
|
||||
# };
|
||||
# };
|
||||
|
||||
# Connect to fritzbox via https proxy (to ensure valid cert)
|
||||
networking.hosts.${globals.net.home-lan.vlans.services.hosts.ward-web-proxy.ipv4} = [
|
||||
fritzboxDomain
|
||||
];
|
||||
|
||||
nodes.ward-web-proxy = {
|
||||
services.nginx = {
|
||||
upstreams."home-assistant" = {
|
||||
servers."${config.wireguard.proxy-home.ipv4}:${toString config.services.home-assistant.config.http.server_port}" =
|
||||
{ };
|
||||
extraConfig = ''
|
||||
zone home-assistant 64k;
|
||||
keepalive 2;
|
||||
'';
|
||||
};
|
||||
virtualHosts.${homeassistantDomain} = {
|
||||
forceSSL = true;
|
||||
useACMEWildcardHost = true;
|
||||
locations."/" = {
|
||||
proxyPass = "http://home-assistant";
|
||||
proxyWebsockets = true;
|
||||
};
|
||||
extraConfig = ''
|
||||
allow ${globals.net.home-lan.vlans.home.cidrv4};
|
||||
allow ${globals.net.home-lan.vlans.home.cidrv6};
|
||||
deny all;
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
42
hosts/sausebiene/mosquitto.nix
Normal file
42
hosts/sausebiene/mosquitto.nix
Normal file
|
@ -0,0 +1,42 @@
|
|||
{ config, ... }:
|
||||
{
|
||||
age.secrets.mosquitto-pw-home-assistant = {
|
||||
mode = "440";
|
||||
owner = "hass";
|
||||
group = "mosquitto";
|
||||
generator.script = "alnum";
|
||||
};
|
||||
|
||||
services.mosquitto = {
|
||||
enable = true;
|
||||
persistence = true;
|
||||
listeners = [
|
||||
{
|
||||
acl = [ "pattern readwrite #" ];
|
||||
users = {
|
||||
# zigbee2mqtt = {
|
||||
# passwordFile = config.age.secrets.mosquitto-pw-zigbee2mqtt.path;
|
||||
# acl = [ "readwrite #" ];
|
||||
# };
|
||||
home_assistant = {
|
||||
passwordFile = config.age.secrets.mosquitto-pw-home-assistant.path;
|
||||
acl = [ "readwrite #" ];
|
||||
};
|
||||
};
|
||||
settings.allow_anonymous = false;
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
networking.nftables.firewall.rules = {
|
||||
# Allow devices and iot VLANs to access the MQTT server
|
||||
access-mqtt = {
|
||||
from = [
|
||||
"vlan-devices"
|
||||
"vlan-iot"
|
||||
];
|
||||
to = [ "local" ];
|
||||
allowedTCPPorts = [ 1883 ];
|
||||
};
|
||||
};
|
||||
}
|
|
@ -1,42 +1,110 @@
|
|||
{
|
||||
config,
|
||||
globals,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
let
|
||||
localVlans = lib.genAttrs [ "services" "home" "devices" "iot" ] (
|
||||
x: globals.net.home-lan.vlans.${x}
|
||||
);
|
||||
in
|
||||
{
|
||||
networking.hostId = config.repo.secrets.local.networking.hostId;
|
||||
|
||||
# FIXME: aaaaaaaaa
|
||||
# globals.monitoring.ping.sausebiene = {
|
||||
# hostv4 = lib.net.cidr.ip globals.net.home-lan.vlans.services.hosts.sausebiene.cidrv4;
|
||||
# hostv6 = lib.net.cidr.ip globals.net.home-lan.vlans.services.hosts.sausebiene.cidrv6;
|
||||
# network = "home-lan.vlans.services";
|
||||
# };
|
||||
globals.monitoring.ping.sausebiene = {
|
||||
hostv4 = lib.net.cidr.ip globals.net.home-lan.vlans.services.hosts.sausebiene.cidrv4;
|
||||
hostv6 = lib.net.cidr.ip globals.net.home-lan.vlans.services.hosts.sausebiene.cidrv6;
|
||||
network = "home-lan.vlans.services";
|
||||
};
|
||||
|
||||
boot.initrd.availableKernelModules = [ "8021q" ];
|
||||
boot.initrd.systemd.network = {
|
||||
enable = true;
|
||||
netdevs."30-vlan-services" = {
|
||||
netdevConfig = {
|
||||
Kind = "vlan";
|
||||
Name = "vlan-services";
|
||||
};
|
||||
vlanConfig.Id = globals.net.home-lan.vlans.services.id;
|
||||
};
|
||||
networks = {
|
||||
inherit (config.systemd.network.networks) "10-lan";
|
||||
"10-lan" = {
|
||||
matchConfig.Name = "lan";
|
||||
networkConfig.LinkLocalAddressing = "no";
|
||||
linkConfig.RequiredForOnline = "carrier";
|
||||
vlan = [ "vlan-services" ];
|
||||
};
|
||||
"30-vlan-services" = {
|
||||
address = [
|
||||
globals.net.home-lan.vlans.services.hosts.sausebiene.cidrv4
|
||||
globals.net.home-lan.vlans.services.hosts.sausebiene.cidrv6
|
||||
];
|
||||
gateway = [ globals.net.home-lan.vlans.services.hosts.ward.ipv4 ];
|
||||
matchConfig.Name = "vlan-services";
|
||||
networkConfig = {
|
||||
IPv6PrivacyExtensions = "yes";
|
||||
MulticastDNS = true;
|
||||
};
|
||||
linkConfig.RequiredForOnline = "routable";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
systemd.network.networks = {
|
||||
"10-lan" = {
|
||||
address = [ "192.168.1.17/24" ];
|
||||
gateway = [ "192.168.1.1" ];
|
||||
matchConfig.MACAddress = config.repo.secrets.local.networking.interfaces.lan.mac;
|
||||
networkConfig = {
|
||||
IPv6PrivacyExtensions = "yes";
|
||||
MulticastDNS = true;
|
||||
systemd.network.netdevs = lib.flip lib.concatMapAttrs localVlans (
|
||||
vlanName: vlanCfg: {
|
||||
# Add an interface for each VLAN
|
||||
"30-vlan-${vlanName}" = {
|
||||
netdevConfig = {
|
||||
Kind = "vlan";
|
||||
Name = "vlan-${vlanName}";
|
||||
};
|
||||
vlanConfig.Id = vlanCfg.id;
|
||||
};
|
||||
linkConfig.RequiredForOnline = "routable";
|
||||
};
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
systemd.network.networks =
|
||||
{
|
||||
"10-lan" = {
|
||||
matchConfig.Name = "lan";
|
||||
# This interface should only be used from attached vlans.
|
||||
# So don't acquire a link local address and only wait for
|
||||
# this interface to gain a carrier.
|
||||
networkConfig.LinkLocalAddressing = "no";
|
||||
linkConfig.RequiredForOnline = "carrier";
|
||||
vlan = map (name: "vlan-${name}") (builtins.attrNames localVlans);
|
||||
};
|
||||
}
|
||||
// lib.flip lib.concatMapAttrs localVlans (
|
||||
vlanName: vlanCfg: {
|
||||
"30-vlan-${vlanName}" = {
|
||||
address = [
|
||||
vlanCfg.hosts.sausebiene.cidrv4
|
||||
vlanCfg.hosts.sausebiene.cidrv6
|
||||
];
|
||||
gateway = [ vlanCfg.hosts.ward.ipv4 ];
|
||||
matchConfig.Name = "vlan-${vlanName}";
|
||||
networkConfig = {
|
||||
IPv6PrivacyExtensions = "yes";
|
||||
MulticastDNS = true;
|
||||
};
|
||||
linkConfig.RequiredForOnline = "routable";
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
networking.nftables.firewall = {
|
||||
zones.untrusted.interfaces = [ "lan" ];
|
||||
zones =
|
||||
{
|
||||
untrusted.interfaces = [ "vlan-services" ];
|
||||
}
|
||||
// lib.flip lib.concatMapAttrs localVlans (
|
||||
vlanName: _: {
|
||||
"vlan-${vlanName}".interfaces = [ "vlan-${vlanName}" ];
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
# Allow accessing influx
|
||||
wireguard.proxy-sentinel.client.via = "sentinel";
|
||||
wireguard.proxy-home.client.via = "ward";
|
||||
}
|
||||
|
|
10
hosts/sausebiene/secrets/home-assistant-secrets.yaml.age
Normal file
10
hosts/sausebiene/secrets/home-assistant-secrets.yaml.age
Normal file
|
@ -0,0 +1,10 @@
|
|||
age-encryption.org/v1
|
||||
-> X25519 /fHu4GoqBkVzZqZJ38xy8XbcWQ6SF3X6rvYjFv8gums
|
||||
4FPbuUEYdrFpv72oo8+VL8rxdQzDFMgy7lfYp/e6PWc
|
||||
-> piv-p256 xqSe8Q A7xG4f2f/SRpM1RIQSVL9q8g/AzVcIrDWq7nGDJQimQo
|
||||
rL9Wgz4z18F5Qn+5Z20N7356YVLLrJvtvtGgx0jJwm4
|
||||
-> u[-grease ad
|
||||
4NbLgEGN91yifuQh9zzwJegrU3ZvxOqtHsCn3XAXpQpv0x9f0HXMGJ2HJnB3dNXL
|
||||
bxLtOZDlNinTOnR0p6ygxhg
|
||||
--- uCx7X+ivq3iUCwYZjIcNZfHgfkzeuTGnG7lsVyKLqTk
|
||||
ßSª#:.éá}"5Â�˜:¦Ÿ¢—ÂM1Cʇug-é6UºFfÎ:kj�qœ’0ô‡”`~’IÐÿTL{P5ðZ3tw¥]¯º*YŠOçYS9¢üË"#ø9Ô|WE"—íz4G“tÖ!k\YÒ—O(�{úð”~¡ßGˆWÕ_îÎàhŸ$@
|
|
@ -1 +1 @@
|
|||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIC+ziFZSELVG9MmbkMDE9xwKHlm4lnr2uHtVNXk+rTu
|
||||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINBDwxSNMyY1EF+xQP+hQ/d/mfK6PapwUWiuDtvealr0
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue