forked from mirrors_public/oddlama_nix-config
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;
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
|
@ -7,7 +7,7 @@
|
|||
...
|
||||
}:
|
||||
let
|
||||
homeDomain = "home.${globals.domains.me}";
|
||||
homeassistantDomain = "home.${globals.domains.personal}";
|
||||
fritzboxDomain = "fritzbox.${globals.domains.me}";
|
||||
in
|
||||
{
|
||||
|
@ -24,11 +24,17 @@ in
|
|||
}
|
||||
];
|
||||
|
||||
topology.self.services.home-assistant.info = "https://${homeDomain}";
|
||||
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 = [
|
||||
"default_config"
|
||||
"radio_browser"
|
||||
"met"
|
||||
"esphome"
|
||||
|
@ -38,8 +44,27 @@ in
|
|||
"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;
|
||||
|
@ -56,56 +81,37 @@ in
|
|||
time_zone = "Europe/Berlin";
|
||||
unit_system = "metric";
|
||||
#external_url = "https://";
|
||||
packages = {
|
||||
manual = "!include manual.yaml";
|
||||
};
|
||||
packages.manual = "!include manual.yaml";
|
||||
};
|
||||
|
||||
#### only selected components from default_config ####
|
||||
|
||||
assist_pipeline = { };
|
||||
backup = { };
|
||||
bluetooth = { };
|
||||
config = { };
|
||||
#cloud = {};
|
||||
#conversation = {};
|
||||
dhcp = { };
|
||||
energy = { };
|
||||
history = { };
|
||||
homeassistant_alerts = { };
|
||||
logbook = { };
|
||||
#media_source = {};
|
||||
mobile_app = { };
|
||||
my = { };
|
||||
ssdp = { };
|
||||
stream = { };
|
||||
sun = { };
|
||||
#usb = {};
|
||||
webhook = { };
|
||||
zeroconf = { };
|
||||
|
||||
### Components not from default_config
|
||||
lovelace.mode = "yaml";
|
||||
|
||||
frontend = {
|
||||
#themes = "!include_dir_merge_named themes";
|
||||
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";
|
||||
# };
|
||||
|
||||
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
|
||||
];
|
||||
};
|
||||
|
||||
|
@ -138,20 +144,20 @@ in
|
|||
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;
|
||||
};
|
||||
};
|
||||
# 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} = [
|
||||
|
@ -168,7 +174,7 @@ in
|
|||
keepalive 2;
|
||||
'';
|
||||
};
|
||||
virtualHosts.${homeDomain} = {
|
||||
virtualHosts.${homeassistantDomain} = {
|
||||
forceSSL = true;
|
||||
useACMEWildcardHost = true;
|
||||
locations."/" = {
|
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";
|
||||
}
|
||||
|
|
|
@ -1 +1 @@
|
|||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIC+ziFZSELVG9MmbkMDE9xwKHlm4lnr2uHtVNXk+rTu
|
||||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINBDwxSNMyY1EF+xQP+hQ/d/mfK6PapwUWiuDtvealr0
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
enable = true;
|
||||
package = pkgs.postgresql_16_jit;
|
||||
|
||||
# Doesn't work with plausible, since it wants to connect as the postgres
|
||||
# Doesn't work with plausible, since it wants to connect as the postgres user
|
||||
# for some (probably unecessary) reason.
|
||||
#
|
||||
# authentication = lib.mkForce ''
|
||||
|
|
Binary file not shown.
|
@ -108,8 +108,9 @@ in
|
|||
globals.services.influxdb.domain
|
||||
globals.services.loki.domain
|
||||
globals.services.paperless.domain
|
||||
"home.${globals.domains.me}"
|
||||
"fritzbox.${globals.domains.me}"
|
||||
globals.services.esphome.domain
|
||||
globals.services.home-assistant.domain
|
||||
"fritzbox.${globals.domains.personal}"
|
||||
];
|
||||
filters = [
|
||||
{
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
}:
|
||||
let
|
||||
inherit (config.repo.secrets.local) acme;
|
||||
fritzboxDomain = "fritzbox.${globals.domains.me}";
|
||||
fritzboxDomain = "fritzbox.${globals.domains.personal}";
|
||||
in
|
||||
{
|
||||
microvm.mem = 1024 * 4; # Need more /tmp space so nginx can store intermediary files
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
age-encryption.org/v1
|
||||
-> X25519 NIQfcq9fdcwAm3/7bqVw9XKuHxH6r2r7Lbqjjr/u+2w
|
||||
Cfz/aTYCh4gNWo+dOzDKXNBaAlt0W/aqTb30ho/i5nM
|
||||
-> piv-p256 xqSe8Q Al+FYiIKhA9B31HjuxCNE65MfYWKIxO+ZefbPsDWljxu
|
||||
+K47WX1YQpRkvIzR4ALVucSj21YIv9WUluEQ62ccEWk
|
||||
-> a"CCg7E9-grease ~ &+9|O
|
||||
fuXdG2v+8S2Bti9ifpvRPfRZfh9ioXzOuYXcPkyPynbQPy2isAksKx83FgQeRoID
|
||||
VHH/CKTjy/qFCDec9MXX2i9GCWWrva1n2tfOXl9kh2IZ1Zl2te2rsA
|
||||
--- Tg/N4zk19YF7LCLd9wb95nyQJs0B59SHO4nh76xif0c
|
||||
ÄíNÑõ9Þ�}òõ¸–wÁÿ2Û
Q/çzbC—AuŸÇ{O&âÎiRïž,E 1šúë9=Ñ”�íÏÓ‡òMC¨ñìÞñÉæî±pæF:9�=È"‡¼[Èß–6»òŸÁ§‚&}ú3E&%º²ýYŽA´í))¸Ä´Í‡mïË
|
||||
_³o¯V@U*½Q1ÄȈ_L²
|
||||
-> X25519 NeZ/7R8+CU7toGb1FkB7QwVloo1McdlWTuCxjN/sK38
|
||||
WPiUyA6EZgPSu3quzi7X+7kCcye96TT1bTd0VmxrYLg
|
||||
-> piv-p256 xqSe8Q AiXv0jbX7EQwvBec7xW0GG8dTN1c+bKc+pTyDI/g/srU
|
||||
l1EaAd6JJYVR5HzJCZqDySb/LyD19sqc7gxR0mKoRjk
|
||||
-> bO76-grease
|
||||
HwGf1RlEpc/KEI3vmJwMRSTsZOlukX6hWN4K5VVuuDWh+wxyPD9Sm7cwlzV9p2tZ
|
||||
XheXpkX3mFHB/ayZL+i48Qo1Fzeti3ZjNMolKBKKRWLqUAGEEVAvJg
|
||||
--- aEo4S/06W/U+PLhGzF1Ff6f4O3GIqcrH2X+To428ShE
|
||||
cÍ Ł{Â`V”yŞ�žÍMßÔxň„÷K.łUi”§Oü–Ě,÷)cŽ0f4úéęě�JÔ;gÇ}'ljÝëŰÝNó2oŻ˙vŐÎß—Ő^ó$0ß°7őáČv Ő¬ˇŐ8p\ěčąäÓ©|äK<rzą*‘Ev.ĂëúD:´)äč~
ő‘9ć-$S‰E>v-Ł }śU>f7b÷Ź�ß°dç7LńŢ»)ÎsÍŔ�ěŁ1GĐď‘�•±qƉîd0G·LÉ�çŹPŢ@ŃĆy*oü
|
|
@ -1,61 +0,0 @@
|
|||
{
|
||||
config,
|
||||
globals,
|
||||
lib,
|
||||
nodes,
|
||||
...
|
||||
}:
|
||||
let
|
||||
sentinelCfg = nodes.sentinel.config;
|
||||
wardWebProxyCfg = nodes.ward-web-proxy.config;
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
../../config
|
||||
../../config/hardware/odroid-n2plus.nix
|
||||
../../config/hardware/physical.nix
|
||||
../../config/optional/zfs.nix
|
||||
|
||||
#./esphome.nix
|
||||
./fs.nix
|
||||
./home-assistant.nix
|
||||
./hostapd.nix
|
||||
#./mosquitto.nix
|
||||
./kea.nix
|
||||
./net.nix
|
||||
#./zigbee2mqtt.nix
|
||||
];
|
||||
|
||||
topology.self.name = "🥔 zackbiene"; # yes this is 2x U+2009, don't ask (satori 🤬).
|
||||
topology.self.hardware.image = ../../topology/images/odroid-n2plus.png;
|
||||
topology.self.hardware.info = "O-Droid N2+";
|
||||
|
||||
nixpkgs.hostPlatform = "aarch64-linux";
|
||||
boot.mode = "efi";
|
||||
|
||||
meta.promtail = {
|
||||
enable = true;
|
||||
proxy = "sentinel";
|
||||
};
|
||||
|
||||
# Connect safely via wireguard to skip http authentication
|
||||
networking.hosts.${
|
||||
if config.wireguard ? proxy-home then
|
||||
wardWebProxyCfg.wireguard.proxy-home.ipv4
|
||||
else
|
||||
sentinelCfg.wireguard.proxy-sentinel.ipv4
|
||||
} = [ globals.services.influxdb.domain ];
|
||||
|
||||
meta.telegraf = {
|
||||
enable = true;
|
||||
influxdb2 = {
|
||||
inherit (globals.services.influxdb) domain;
|
||||
organization = "machines";
|
||||
bucket = "telegraf";
|
||||
node = "sire-influxdb";
|
||||
};
|
||||
};
|
||||
|
||||
# Fails if there are no SMART devices
|
||||
services.smartd.enable = lib.mkForce false;
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
{
|
||||
config,
|
||||
nodes,
|
||||
...
|
||||
}:
|
||||
let
|
||||
sentinelCfg = nodes.sentinel.config;
|
||||
esphomeDomain = "esphome.${sentinelCfg.repo.secrets.global.domains.personal}";
|
||||
in
|
||||
{
|
||||
environment.persistence."/persist".directories = [
|
||||
{
|
||||
directory = "/var/lib/private/esphome";
|
||||
mode = "0700";
|
||||
}
|
||||
];
|
||||
|
||||
topology.self.services.esphome.info = "https://${esphomeDomain}";
|
||||
services.esphome = {
|
||||
enable = true;
|
||||
enableUnixSocket = true;
|
||||
#allowedDevices = lib.mkForce ["/dev/serial/by-id/usb-Silicon_Labs_CP2102_USB_to_UART_Bridge_Controller_0001-if00-port0"];
|
||||
# TODO instead deny the zigbee device
|
||||
};
|
||||
|
||||
#security.acme.certs."home.${personalDomain}".extraDomainNames = [
|
||||
# "esphome.home.${personalDomain}"
|
||||
#];
|
||||
systemd.services.nginx = {
|
||||
serviceConfig.SupplementaryGroups = [ "esphome" ];
|
||||
requires = [ "esphome.service" ];
|
||||
};
|
||||
|
||||
services.nginx = {
|
||||
upstreams."esphome" = {
|
||||
servers."unix:/run/esphome/esphome.sock" = { };
|
||||
extraConfig = ''
|
||||
zone esphome 64k;
|
||||
keepalive 2;
|
||||
'';
|
||||
};
|
||||
virtualHosts."${esphomeDomain}" = {
|
||||
forceSSL = true;
|
||||
#enableACME = true;
|
||||
sslCertificate = config.age.secrets."selfcert.crt".path;
|
||||
sslCertificateKey = config.age.secrets."selfcert.key".path;
|
||||
locations."/" = {
|
||||
proxyPass = "http://esphome";
|
||||
proxyWebsockets = true;
|
||||
};
|
||||
# TODO dynamic definitions for the "local" network, IPv6
|
||||
extraConfig = ''
|
||||
deny all;
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
let
|
||||
inherit (config.repo.secrets.local) disks;
|
||||
in
|
||||
{
|
||||
disko.devices = {
|
||||
disk = {
|
||||
mmc = {
|
||||
type = "disk";
|
||||
device = "/dev/disk/by-id/${disks.mmc}";
|
||||
content = {
|
||||
type = "gpt";
|
||||
partitions = {
|
||||
efi = lib.disko.gpt.partEfi "1G";
|
||||
swap = lib.disko.gpt.partSwap "8G";
|
||||
rpool = lib.disko.gpt.partLuksZfs disks.mmc "rpool" "100%";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
zpool = {
|
||||
rpool = lib.disko.zfs.mkZpool { datasets = lib.disko.zfs.impermanenceZfsDatasets; };
|
||||
};
|
||||
};
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
{ config, ... }:
|
||||
{
|
||||
# Associates a mandatory and unique password to each client
|
||||
# TODO: autogenerate? via secret generators and derived secrets?
|
||||
age.secrets.wifi-clients.rekeyFile = ./secrets/wifi-clients.age;
|
||||
|
||||
hardware.wirelessRegulatoryDatabase = true;
|
||||
|
||||
services.hostapd = {
|
||||
enable = true;
|
||||
radios.wlan1 = {
|
||||
band = "2g";
|
||||
countryCode = "DE";
|
||||
channel = 13; # Automatic Channel Selection (ACS) is unfortunately not implemented for mt7612u.
|
||||
wifi4.capabilities = [
|
||||
"LDPC"
|
||||
"HT40+"
|
||||
"HT40-"
|
||||
"GF"
|
||||
"SHORT-GI-20"
|
||||
"SHORT-GI-40"
|
||||
"TX-STBC"
|
||||
"RX-STBC1"
|
||||
];
|
||||
networks.wlan1 = {
|
||||
inherit (config.repo.secrets.local.hostapd) ssid;
|
||||
macAcl = "allow";
|
||||
apIsolate = true;
|
||||
authentication = {
|
||||
saePasswordsFile = config.age.secrets.wifi-clients.path;
|
||||
saeAddToMacAllow = true;
|
||||
enableRecommendedPairwiseCiphers = true;
|
||||
};
|
||||
bssid = "00:c0:ca:b1:4f:9f";
|
||||
};
|
||||
#networks.wlan1-2 = {
|
||||
# inherit (config.repo.secrets.local.hostapd) ssid;
|
||||
# authentication.mode = "none";
|
||||
# bssid = "02:c0:ca:b1:4f:9f";
|
||||
#};
|
||||
};
|
||||
};
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
{
|
||||
lib,
|
||||
utils,
|
||||
...
|
||||
}:
|
||||
let
|
||||
inherit (lib) net;
|
||||
iotCidrv4 = "10.0.90.0/24"; # FIXME: make all subnet allocations accessible via global.net or smth
|
||||
in
|
||||
{
|
||||
environment.persistence."/persist".directories = [
|
||||
{
|
||||
directory = "/var/lib/private/kea";
|
||||
mode = "0700";
|
||||
}
|
||||
];
|
||||
|
||||
services.kea.dhcp4 = {
|
||||
enable = true;
|
||||
settings = {
|
||||
lease-database = {
|
||||
name = "/var/lib/kea/dhcp4.leases";
|
||||
persist = true;
|
||||
type = "memfile";
|
||||
};
|
||||
valid-lifetime = 86400;
|
||||
renew-timer = 3600;
|
||||
interfaces-config = {
|
||||
interfaces = [ "wlan1" ];
|
||||
service-sockets-max-retries = -1;
|
||||
};
|
||||
subnet4 = [
|
||||
{
|
||||
id = 1;
|
||||
interface = "wlan1";
|
||||
subnet = iotCidrv4;
|
||||
pools = [
|
||||
{ pool = "${net.cidr.host 20 iotCidrv4} - ${net.cidr.host (-6) iotCidrv4}"; }
|
||||
];
|
||||
option-data = [
|
||||
{
|
||||
name = "routers";
|
||||
data = net.cidr.host 1 iotCidrv4;
|
||||
}
|
||||
];
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
systemd.services.kea-dhcp4-server.after = [
|
||||
"sys-subsystem-net-devices-${utils.escapeSystemdPath "wlan1"}.device"
|
||||
];
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
{ config, ... }:
|
||||
{
|
||||
age.secrets.mosquitto-pw-zigbee2mqtt = {
|
||||
rekeyFile = ./secrets/mosquitto-pw-zigbee2mqtt.age;
|
||||
mode = "440";
|
||||
owner = "zigbee2mqtt";
|
||||
group = "mosquitto";
|
||||
};
|
||||
age.secrets.mosquitto-pw-home_assistant = {
|
||||
rekeyFile = ./secrets/mosquitto-pw-home_assistant.age;
|
||||
mode = "440";
|
||||
owner = "hass";
|
||||
group = "mosquitto";
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
];
|
||||
};
|
||||
}
|
|
@ -1,94 +0,0 @@
|
|||
{
|
||||
config,
|
||||
globals,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
let
|
||||
iotCidrv4 = "10.90.0.0/24";
|
||||
iotCidrv6 = "fd00:90::/64";
|
||||
in
|
||||
{
|
||||
networking.hostId = config.repo.secrets.local.networking.hostId;
|
||||
|
||||
globals.monitoring.ping.zackbiene = {
|
||||
hostv4 = "zackbiene.local";
|
||||
hostv6 = "zackbiene.local";
|
||||
network = "home-lan.vlans.services";
|
||||
};
|
||||
|
||||
wireguard.proxy-home.client.via = "ward";
|
||||
|
||||
boot.initrd.systemd.network = {
|
||||
enable = true;
|
||||
networks = {
|
||||
inherit (config.systemd.network.networks) "10-lan1";
|
||||
};
|
||||
};
|
||||
|
||||
systemd.network.networks = {
|
||||
"10-lan1" = {
|
||||
DHCP = "yes";
|
||||
dhcpV4Config.UseDNS = false;
|
||||
dhcpV6Config.UseDNS = false;
|
||||
ipv6AcceptRAConfig.UseDNS = false;
|
||||
matchConfig.MACAddress = config.repo.secrets.local.networking.interfaces.lan1.mac;
|
||||
networkConfig = {
|
||||
IPv6PrivacyExtensions = "yes";
|
||||
MulticastDNS = true;
|
||||
};
|
||||
linkConfig.RequiredForOnline = "routable";
|
||||
};
|
||||
"10-wlan1" = {
|
||||
address = [
|
||||
(lib.net.cidr.hostCidr 1 iotCidrv4)
|
||||
(lib.net.cidr.hostCidr 1 iotCidrv6)
|
||||
];
|
||||
matchConfig.MACAddress = config.repo.secrets.local.networking.interfaces.wlan1.mac;
|
||||
networkConfig = {
|
||||
IPv4Forwarding = "yes";
|
||||
IPv6PrivacyExtensions = "yes";
|
||||
IPv6SendRA = true;
|
||||
MulticastDNS = true;
|
||||
};
|
||||
# Announce a static prefix
|
||||
ipv6Prefixes = [
|
||||
{ Prefix = iotCidrv6; }
|
||||
];
|
||||
linkConfig.RequiredForOnline = "no";
|
||||
};
|
||||
};
|
||||
|
||||
networking.nftables.firewall = {
|
||||
snippets.nnf-icmp.ipv6Types = [
|
||||
"mld-listener-query"
|
||||
"nd-router-solicit"
|
||||
];
|
||||
|
||||
zones = {
|
||||
untrusted.interfaces = [ "lan1" ];
|
||||
lan-interface.interfaces = [ "lan1" ];
|
||||
lan = {
|
||||
parent = "lan-interface";
|
||||
ipv4Addresses = [ globals.net.home-lan.vlans.services.cidrv4 ];
|
||||
ipv6Addresses = [ globals.net.home-lan.vlans.services.cidrv6 ];
|
||||
};
|
||||
iot.interfaces = [ "wlan1" ];
|
||||
};
|
||||
|
||||
rules = {
|
||||
masquerade-iot = {
|
||||
from = [ "lan" ];
|
||||
to = [ "iot" ];
|
||||
masquerade = true;
|
||||
};
|
||||
|
||||
outbound = {
|
||||
from = [ "lan" ];
|
||||
to = [ "iot" ];
|
||||
late = true; # Only accept after any rejects have been processed
|
||||
verdict = "accept";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILaKQa+gcGMvtm9d1LM11lvsXRtE3Tvo+o40nG+eXYgo
|
|
@ -1,11 +0,0 @@
|
|||
age-encryption.org/v1
|
||||
-> X25519 tE4e6O7Qv5OeZYC9hPYaN7SdCDq1Nl9rYBkGfKHo2XM
|
||||
b7c3Hwfvm8GmRjpe/0I1FbADK6S5ZI9axR7cLO9kja8
|
||||
-> piv-p256 xqSe8Q Ar49h1MV117LH71oyK7kkBpKJv4t1XyDrNxWOK6cAMTL
|
||||
xkQzAhG8snR+B+Bzv1OIYScslCN7yKk2iMvWH/WyRm4
|
||||
-> `G&J4E-grease 5AwMc]> Tgl>j1Z:
|
||||
GShgbkXFvntIBUbz2aSg4QHWdLvyxSg
|
||||
--- tAQKeRaBrOxd7wnnfMn2M36WlqzFFl7GZxak7wUkg+s
|
||||
öc“ß?›"e’P»r]î>Ãùæ—%�(æ’)ÛÓŠIÃÎBÁ
ù
|
||||
ãnÜ‘ÈI¸Å½…÷gA´¹vÚûÊ®ºg¦ç9wVFæZ«‰ñ‹šrŸÓ4Û}1ãQZšª3øõ1‚€”‡
†Ù§[ÔúÑSËä$‹�Ÿ}j÷á_V¿SeùÈ+ �ºH/
|
||||
…bLYU²Œi·«¦ÍaýÕYËÊ|hñ�8TrÜ-íø;ý'„Y~Ë/'ðïÆATQŸï7®Æ¨{{x�X`õ&çqH“hw³¨�Øxvr"¢ëhSXYŒ¼9�fï’¸#,#ÐÅöUƒ[KI™Æœ:•jè7¥=žWHÂ/¦'C
a°Dqœ‡¶™êüm$`?›j¥ñŸ%›¯:
|
Binary file not shown.
|
@ -1,11 +0,0 @@
|
|||
age-encryption.org/v1
|
||||
-> X25519 fnG14tqQJow7aCttB48iukNYbIENNYSCOdnGmzsUR08
|
||||
CUgbzHmMTVDjVvwXoJ1Li1HJuCQcexOwTA8vyI1qBy0
|
||||
-> piv-p256 xqSe8Q A2lUZF0cZPhAduYPGQg/vrpLPVidJQuIXMh1KCIw2fJu
|
||||
SVtOdeJXECGJtNsJkDGnrljvO1xWqmCueMS7dISppP0
|
||||
-> 97L6-grease 9 Uv0 :8=|&
|
||||
5sV9Y2boLn0oRELbKB1PHp/1YbofZfNprKwUjrcXHTl2qsc02mVOVGBcoghUg7qa
|
||||
z99fVBeVj+nR/E6In8lDKR7mUf7ZF8oHxIDEGQcQ9hysO3jbWFA6CMH48h9ICcen
|
||||
hEI
|
||||
--- gP2qI8vwLWirtwKRpx3iyNc+MUi03qQ353vfzxjYA+8
|
||||
RN¯±c<�’{rÏ2_Zèj|žÆAõ.�ê*=9C‘öÈÞ¸qp‰Ê•/PÓ@¬ÈO-±
|
Binary file not shown.
Binary file not shown.
|
@ -1,62 +0,0 @@
|
|||
{
|
||||
config,
|
||||
nodes,
|
||||
...
|
||||
}:
|
||||
let
|
||||
sentinelCfg = nodes.sentinel.config;
|
||||
zigbeeDomain = "zigbee.${sentinelCfg.repo.secrets.global.domains.personal}";
|
||||
in
|
||||
{
|
||||
age.secrets."mosquitto-pw-zigbee2mqtt.yaml" = {
|
||||
rekeyFile = ./secrets/mosquitto-pw-zigbee2mqtt.yaml.age;
|
||||
mode = "440";
|
||||
owner = "zigbee2mqtt";
|
||||
group = "mosquitto";
|
||||
};
|
||||
|
||||
#security.acme.certs."home.${personalDomain}".extraDomainNames = [
|
||||
# "zigbee.home.${personalDomain}"
|
||||
#];
|
||||
topology.self.services.zigbee2mqtt.info = "https://${zigbeeDomain}";
|
||||
services.zigbee2mqtt = {
|
||||
enable = true;
|
||||
settings = {
|
||||
advanced.log_level = "warn";
|
||||
homeassistant = true;
|
||||
permit_join = true;
|
||||
serial = {
|
||||
port = "/dev/serial/by-id/usb-Silicon_Labs_Sonoff_Zigbee_3.0_USB_Dongle_Plus_0001-if00-port0";
|
||||
};
|
||||
mqtt = {
|
||||
server = "mqtt://localhost:1883";
|
||||
user = "zigbee2mqtt";
|
||||
password = "!${config.age.secrets."mosquitto-pw-zigbee2mqtt.yaml".path} password";
|
||||
};
|
||||
# TODO once 1.30.3 is out
|
||||
# frontend.host = "/run/zigbee2mqtt/zigbee2mqtt.sock";
|
||||
frontend.port = 8072;
|
||||
};
|
||||
};
|
||||
|
||||
services.nginx = {
|
||||
upstreams."zigbee2mqtt" = {
|
||||
servers."localhost:8072" = { };
|
||||
extraConfig = ''
|
||||
zone zigbee2mqtt 64k;
|
||||
keepalive 2;
|
||||
'';
|
||||
};
|
||||
virtualHosts."${zigbeeDomain}" = {
|
||||
forceSSL = true;
|
||||
#enableACME = true;
|
||||
sslCertificate = config.age.secrets."selfcert.crt".path;
|
||||
sslCertificateKey = config.age.secrets."selfcert.key".path;
|
||||
locations."/".proxyPass = "http://zigbee2mqtt";
|
||||
# TODO dynamic definitions for the "local" network, IPv6
|
||||
extraConfig = ''
|
||||
deny all;
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue