feat: configure homeassistant and esphome on new machine

This commit is contained in:
oddlama 2025-01-26 01:43:01 +01:00
parent 0ff0828ca9
commit 3d37e2959f
No known key found for this signature in database
GPG key ID: 14EFE510775FE39A
52 changed files with 403 additions and 672 deletions

View file

@ -19,6 +19,10 @@
./fs.nix
./net.nix
./esphome.nix
./home-assistant.nix
./mosquitto.nix
];
topology.self.hardware.info = "Intel N100, 16GB RAM";

View 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;
'';
};
};
};
}

View file

@ -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."/" = {

View 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 ];
};
};
}

View file

@ -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";
}

View file

@ -1 +1 @@
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIC+ziFZSELVG9MmbkMDE9xwKHlm4lnr2uHtVNXk+rTu
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINBDwxSNMyY1EF+xQP+hQ/d/mfK6PapwUWiuDtvealr0

View file

@ -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.

View file

@ -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 = [
{

View file

@ -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

View file

@ -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=Ñ”�íÏÓ‡òM C¨ñìÞñÉæî±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ü

View file

@ -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;
}

View file

@ -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;
'';
};
};
}

View file

@ -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; };
};
};
}

View file

@ -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";
#};
};
};
}

View file

@ -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"
];
}

View file

@ -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;
}
];
};
}

View file

@ -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";
};
};
};
}

View file

@ -1 +0,0 @@
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILaKQa+gcGMvtm9d1LM11lvsXRtE3Tvo+o40nG+eXYgo

View file

@ -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¥ñŸ%›¯:

View file

@ -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-± 

View file

@ -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;
'';
};
};
}