feat: z2m!

This commit is contained in:
Truxnell 2024-04-25 22:01:37 +10:00
parent f923a0e25a
commit 3c067ad506
8 changed files with 338 additions and 208 deletions

View file

@ -14,7 +14,6 @@
}; };
mySystem.system.systemd.pushover-alerts.enable = false; mySystem.system.systemd.pushover-alerts.enable = false;

View file

@ -25,6 +25,7 @@
redlib.enable = true; redlib.enable = true;
mosquitto.enable = true; mosquitto.enable = true;
zigbee2mqtt.enable = true;
}; };

View file

@ -70,12 +70,6 @@ in
}; };
environmentFiles = [ config.sops.secrets."services/${app}/env".path ]; environmentFiles = [ config.sops.secrets."services/${app}/env".path ];
ports = [ (builtins.toString port) ]; # expose port ports = [ (builtins.toString port) ]; # expose port
labels = lib.myLib.mkTraefikLabels {
name = app;
domain = config.networking.domain;
inherit port;
};
}; };
networking.firewall = mkIf cfg.openFirewall { networking.firewall = mkIf cfg.openFirewall {

View file

@ -16,5 +16,6 @@
./powerdns ./powerdns
./adguardhome ./adguardhome
./mosquitto ./mosquitto
./zigbee2mqtt
]; ];
} }

View file

@ -18,7 +18,8 @@ in
sops.secrets."services/mosquitto/mq/hashedPassword" = { sops.secrets."services/mosquitto/mq/hashedPassword" = {
sopsFile = ./secrets.sops.yaml; sopsFile = ./secrets.sops.yaml;
owner = config.users.users.mosquitto.name; owner = "mosquitto";
group = "mosquitto";
restartUnits = [ "${app}.service" ]; restartUnits = [ "${app}.service" ];
}; };

View file

@ -1,7 +1,8 @@
services: services:
mosquitto: mosquitto:
mq: mq:
hashedPassword: ENC[AES256_GCM,data:l6QVTtfZJhsMfKoN/pIuKevjq6avIroUMSJQpj/53Lhuw/Okw2E9o1QBECBYvNUoU/367rCR82TBmPF5jguunWLI15bxnKxBvcPda33SlYwPGuiXDaALfk0WAPs11mpwvpgNNiVPOD5gDCW7YSNG8w==,iv:VF9Cm8Yp7SZY/CH5V6aLTGWb0CA4N50Kl7RPJJ/aKBc=,tag:dq9HwYCakSSel3cv/t3BjQ==,type:str] hashedPassword: ENC[AES256_GCM,data:FI2KLDYBW+HCXbOfmdyRYEwI8IeEX2iNWAiyGyn6FE2kkP1K1GETLbissBds+6DAPgbBULhxh8PZ9nkOCtR9QGKLkImxgMU1o8Zmwc6UxfkmFWvEDyt0/3g5gEVoO2rsLx4GMp4qO6Wbz0QCxJWJwQ==,iv:Pt9g/c3A1aChcgxQMSa76f1/c7uq8ctf9CDTFKNSvcs=,tag:zSS9heWyjvwFwKoEmYo+GA==,type:str]
plainPassword.yaml: ENC[AES256_GCM,data:z+/SQE+PfsIoDekjf5D6f6nkxsfiNmcym4YSVdIl,iv:heh5N8HazUUP251llJLVGdka+65Ofjm4by1kug5uKYI=,tag:NS5K47rZRLH6OzyQopB3+A==,type:str]
sops: sops:
kms: [] kms: []
gcp_kms: [] gcp_kms: []
@ -11,68 +12,68 @@ sops:
- recipient: age1lj5vmr02qkudvv2xedfj5tq8x93gllgpr6tzylwdlt7lud4tfv5qfqsd5u - recipient: age1lj5vmr02qkudvv2xedfj5tq8x93gllgpr6tzylwdlt7lud4tfv5qfqsd5u
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBFVlVjV3BBQjJHT1ZOVm9n YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBjNmJPbGFaRXkrYnh6RHR4
RGhhOGcxanhNWEIxTTBUMU9DcEd3Tjc1c25VCjZVOXhVajVhVzR4WWlEbTQ5amFD dHBWNWs0RDB1UnFycXdoWmFFYkNGcHh4YUZjCm9LYnZIU1dvNzVTNHp0NjBEMTE2
WWN4bXJueVBrSHBocU93MDB5ZDdjT0EKLS0tIFhXY2xNZnVLYnIyYmxTSXU3Q3g4 MHpOdVh5aVR3c2o5V21LU1ZDWnpXdFEKLS0tIDB4emRQRGVVYzFYQ2JsakVIeFhn
dElVV3ZsNFZZM083Tm10dVJJeDlkTWMK3/QJ1Gni+zj7J7gc0x3xL35rfBr6UNVa QlFEYjBSVjVKUFVEV3gwK2NZaW1XSGMKHBPiLSd7Ixs68xMcpOJzw2Vu4GbKAglY
4ii+q0pHpMBTMb0S1nGbazi3wb1I5KxnINzS6mFSWXkMFU63l3b2YA== 1yAcQgIRvFxCV/uz4BwAFvyCtYYsUD4GhEAFp/wwq6W1hTEAseV0RA==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
- recipient: age17edew3aahg3t5nte5g0a505sn96vnj8g8gqse8q06ccrrn2n3uysyshu2c - recipient: age17edew3aahg3t5nte5g0a505sn96vnj8g8gqse8q06ccrrn2n3uysyshu2c
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBPejZ3NVMrZnRtMkNQV3dp YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBGRGE0OWZaR0ZhY2piMkxY
ejJraDFpcG5mNEs5WE5YMGJKTEx5OGs4ZkJNCk05NDA4NTVxTFQ4UU5Mck9hYlZS cG1HRFIrWjJFeG8vbzhPWW51cWVvN2FXV2hvCjhLNmVwRkRWQm54N3FPczFudGVB
Skx0L0xDVkxqU0ZROTBBTjQzaHdNSUkKLS0tIHpRQWtub3Bmc29aSjdBeHhKK1pq N096dmFaUktqMDBIRWxlTnVaa2VOcVkKLS0tIEFZSmYweDRqVlpOdUhESXFjbnR0
L0dLRm4wRTZsS0thc3VUSit5cDgxL2MKXGdQz7a9oEoqxNnGCQODcpb4W1RUKcli RXpBU2t0bFlJQW1RSXZ0dzZFT3RXT0UKraC9wurjhf+SiKPewE5L3auOI59kaj/h
josRkGYVeOfRY0+1BKwk5XIbAKMohz+YbTKUKkWDYEXiffSjd7Ae2Q== 7nUUnWUQfkebO5wxtmbbmenXK//oY1tRbC/Dp2JPUTQo2wwrXMF/JQ==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
- recipient: age1u4tht685sqg6dkmjyer96r93pl425u6353md6fphpd84jh3jwcusvm7mgk - recipient: age1u4tht685sqg6dkmjyer96r93pl425u6353md6fphpd84jh3jwcusvm7mgk
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBtNWhPRkt3M1NJajBlOCty YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBEVjdTRkdDMElXV0pLL3Fv
RlEwQ1ZCQXhNQnIxbzVwcHpJUE11U2ZvcmxnClYyZ3dqSC91K2I2TmhSMEVIUWJy MFpxaStjaHNPSmY1RnFUbUhVS09hUEZYYVFVCk4yUEsxV2FvYm5ybVdGS0pmVW5m
N1dMbjdsNjVUdVM3Uk9BeElRaUhzRXcKLS0tIGxhS1pTUEt3aU9nckNHMnQ2Q0xw b1FYb3RxYXFhVVhjNms1bjBtVEYyMm8KLS0tIDk2Uy9xT3VZdGdSY1Rjc3l5OGE4
WjdCRTRMdzVMekk1YXZBejdxdWF4UEkK7SPmOHbup24/IhITZzOnl7YSSYXl/ShW ZzJvWTdDRlFNMmliUGZZbnRCc0UwRHcKdJllfqPlYBNf/nWkT5E1Is1moIFvN6lc
gOqyzkXxe079LHOadm/nqn+XVgnYdTeGf4budabp1TxUaMPsxgjG8g== XVGQ9tzH1tpZC9PwmC1nL08fmuzwI5k9zqL2D+eG+vH7yjBLRFzxVg==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
- recipient: age1cp6vegrmqfkuj8nmt2u3z0sur7n0f7e9x9zmdv4zygp8j2pnucpsdkgagc - recipient: age1cp6vegrmqfkuj8nmt2u3z0sur7n0f7e9x9zmdv4zygp8j2pnucpsdkgagc
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBkcFpSOE16bVlKalc4eEJn YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAzOUJOTFJFSHp1QTdKVmZk
cVdwdWJwV3grR0dUUkZ2V1kvVyt5UVNmQVNrCnJLakF1aWhKaGYyYnR3Zlh0dUxI UVZhVEpqQzVTOTZHOXBYbDJUVjBpL2ErV2tJCm1jOUVmVE8wc3ZLSDZJRVkxQkNU
WStFbmVjWEM3QkU0NmlodjY0OXJLOWsKLS0tIFRPOC96M3IxcXdsaDB5RnJJb1dZ eEw2RVNpVzJFcTlPbnF1Q2g0RVo5OUEKLS0tIFpvcU11RGVNR0dNQWc1aGJFd1lm
a3hpczhWUDVQM3JiVU5ja3ZDMVRGb3MKowyjuHf0SO6zJ4+dnnuxWUn17uTDh0Iy Ym1kellUNjRsTTdQZTViZnBkRllqbWsKWFwR8WeIxJunlgzT3GpYkIdFRLJZlhm4
4x0cAKbwuSLlna3miG0Vxvfy/EehDuiZFW3c3EM7ITdayodM4lQNwQ== Vr4N5CMnNXJYWRiNJDrHDKvVw6EeUUhyJ+7aJ9UAfR0YfX47yIlW5A==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
- recipient: age1ekt5xz7u2xgdzgsrffhd9x22n80cn4thxd8zxjy2ey5vq3ca7gnqz25g5r - recipient: age1ekt5xz7u2xgdzgsrffhd9x22n80cn4thxd8zxjy2ey5vq3ca7gnqz25g5r
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBXMy9XTFEvMExXQUJqbnlL YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBnWUpaOUVnRS9Va211UXl0
YTdLd0c4bnVkOENtVGlocnRVQmhocHRiTlNnCm5Cc08yK3lLTkU0ZkNzQUViOC9q dUFmOVlmR2d1REVNRXJKMFV1OTVsSDZGRldvCk5CcGpWZHFBYmtsbVRjWW1QejNJ
a1JGRFlaVnJyMVRod2NKWHJ6VVlSMzQKLS0tIFI4Z01hKzN5Y1VZKzIwb1VvVnZz Y0FPSVJ1UlhhcVhRaTVXMERyRURIRjAKLS0tIDloZ2J0anNqSEZmUXg4eDFyTVZ0
bXFlZ1hCV3ZGOXJBMDY3M1RzOVZzbWMKlCRZZ+cKYyxd5VusNklUqJkVGp4/A7/U WXAvTmRpUjRKMnJlZmV0SEEwZndtNkEK99oVyOIlfKP6zHnuS1LKGORuOLfX3vAU
TBmsn2lHgLi+mnoCN6YLNcLz0gxG27VFMAQSaDECMW6HP0Yy2soAKA== Gw7IHXRUSpZhElOSK0jc9F2O6IEGYpfu5PYC4m8uojhRXsG6W/XTXA==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
- recipient: age1jpeh4s553taxkyxhzlshzqjfrtvmmp5lw0hmpgn3mdnmgzku332qe082dl - recipient: age1jpeh4s553taxkyxhzlshzqjfrtvmmp5lw0hmpgn3mdnmgzku332qe082dl
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBZSnZKNjRGamJGZ2gxbnBR YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSArUVZScTVpWjZvUzlFeDVQ
S2t5TkpTcll6QVpmclhrWEtGODQrUkVyVVRjCmgwcWpBTWZhMFc4NC9MVDV4UVox RjRreTBPTEF3bXZ1Sk5sWmgzUURxcU5talFzClFRSjFFUEtQMis4dkNLRFFaaHlq
WVY1WjMxajVsNG9YRTJOYktrSTVZRUkKLS0tIE01dkZXRTFmM3lWN0RqLzJWQVF0 QXdhQWxEMXdHSGgzSXlqZm4xbEFVaGMKLS0tIG9OOHUwZWI3SXJ2SWZvdS9EVzV5
aSt3SUE2N3BJeFJUVnR6K2UzUDl0a28K2WcNLOYihCBoL5KMTQWvtbgqtTosA3Y6 aVpWNmk5b1owbjJHSWtnOHRJWmhEbGMKT4VdLG/8qX+KWO6vQ2TsrT1+w4XgDani
s+2XzBnz/RonDVe2Wh+trkwfKfiwyEvhcyBHQIjU6g4eWovVDMq7Vw== KOAS/DimV22EjmHljs9ByWdqkVw0KIBfp2oXo9m0Jqn0H1M2yEMozg==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
- recipient: age1j2r8mypw44uvqhfs53424h6fu2rkr5m7asl7rl3zn3xzva9m3dcqpa97gw - recipient: age1j2r8mypw44uvqhfs53424h6fu2rkr5m7asl7rl3zn3xzva9m3dcqpa97gw
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBkaEVuZWRaeExoaStDQW14 YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBWNzZiS1pVTXMxQW9UUldr
SFdwM1VxTFQ4dGdNRUdQOUF1djVnOUQ5M3hFCmc4NWxtTDNRcjlmVGxLSW45c0lm dFNHUDhxV1lnOFNaK2J6SEY3WUtId0FLSTNjCkJhUm1sVnlJMFk5Wm9lNGdMYnZv
ZzRURjZmTTFibk43Q0RiZkk1NmdOL2sKLS0tIDZ0L3U2M2JlamJwTVUwRFVHR1FM eVR6MGNVYjBSNENqU1BJWituMnJJeFUKLS0tIEk0ZUVETHRmSmJjT2NDMGNrajlZ
aUsvNG95K0lxdUFjRUpXdHhDZjIyV0EKEYU4IkFdNXqWHD6+ukmcOkiB7UW5Fn1w NUMxdEhvQnRES2hwck5jeEw3bnprZ00KUnkLNK+hmCihLwPiWt3NHjASuQwgBOlf
M009nesLsOp1j1sVEStgPzPJLnw/j2OZQgkiMSzeLE1CrGLaOLdpCw== AsW8BkUjjQY/ABR3X6sNSI8uXQt+8BHP14U4cO8jwggcyE2W1Pq03A==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
lastmodified: "2024-04-25T03:13:36Z" lastmodified: "2024-04-25T05:21:30Z"
mac: ENC[AES256_GCM,data:YC1lPTCSUZPgZte0WHGbqh1k3vAfcvVNyxR7+daPH5xwrXyZa/jTIY5U/UuD+nVCr2YFB89QKGko6c76sqoDxhMRf/FDZt0YSa+RaOJO0LNinRBokV2sm0oYxeWoKEUB1B5cuXVfBqqsgw6qQqvIuysXGk0A8vjXUT/btZSd2/A=,iv:Gu3X3xxZ5dmb2Y6Ve/7xznmS0/x5uU9iIDRiOE/DR9Y=,tag:1l7GtZTxBFLvV4JzRVt4Ow==,type:str] mac: ENC[AES256_GCM,data:aN2YsPhDMl8vvLgi98iRCl5f7llJ8VTt7y1vh7liBAwG6hW1vZeetqSxvWiVDugozjsbK6n44AUDjhnfwo8EXp8iWQmJDXuS1PoEph4C18oo4Eu8n9ZpLQFGSKRMYXuetwrzOkn42tMYadYbwLPIandKqaTGUD4FcoqZtGxaH3c=,iv:Wfb1r8JNO74KjBWTIGwW2bXTVv3AIpj5KW9FXYrDm7c=,tag:FEnXv1S0J3XaxZ6TQz0bhQ==,type:str]
pgp: [] pgp: []
unencrypted_suffix: _unencrypted unencrypted_suffix: _unencrypted
version: 3.8.1 version: 3.8.1

View file

@ -7,7 +7,88 @@
with lib; with lib;
let let
cfg = config.mySystem.services.traefik; cfg = config.mySystem.services.traefik;
routersFile = builtins.toFile "routers.yaml" (builtins.toJSON cfg.routers);
# core dynamic options to define middleware
# sso etc
dynamicOptions = [{
http.middlewares = {
# Whitelist local network and VPN addresses
local-ip-only.ipWhiteList.sourceRange = [
"127.0.0.1/32" # localhost
"192.168.0.0/16" # RFC1918
"10.0.0.0/8" # RFC1918
"172.16.0.0/12" # RFC1918 (docker network)
];
# authelia = {
# # Forward requests w/ middlewares=authelia@file to authelia.
# forwardAuth = {
# # address = cfg.autheliaUrl;
# address = "http://localhost:9092/api/verify?rd=https://auth.dhupar.xyz:444/";
# trustForwardHeader = true;
# authResponseHeaders = [
# "Remote-User"
# "Remote-Name"
# "Remote-Email"
# "Remote-Groups"
# ];
# };
# };
# authelia-basic = {
# # Forward requests w/ middlewares=authelia-basic@file to authelia.
# forwardAuth = {
# address = "http://localhost:9092/api/verify?auth=basic";
# trustForwardHeader = true;
# authResponseHeaders = [
# "Remote-User"
# "Remote-Name"
# "Remote-Email"
# "Remote-Groups"
# ];
# };
# };
# https://oauth2-proxy.github.io/oauth2-proxy/docs/configuration/overview/#forwardauth-with-static-upstreams-configuration
# auth-headers = {
# browserXssFilter = true;
# contentTypeNosniff = true;
# forceSTSHeader = true;
# frameDeny = true;
# sslHost = domain;
# sslRedirect = true;
# stsIncludeSubdomains = true;
# stsPreload = true;
# stsSeconds = 315360000;
# };
};
tls.options.default = {
minVersion = "VersionTLS13";
sniStrict = true;
};
# Set up wildcard domain certificates for both *.hostname.domain and *.local.domain
http.routers = {
traefik = {
entrypoints = "websecure";
rule = "Host(`traefik-${config.networking.hostName}.${config.mySystem.domain}`)";
tls.certresolver = "letsencrypt";
tls.domains = [{
main = "${config.mySystem.domain}";
sans = "*.${config.mySystem.domain}";
}];
middlewares = "local-ip-only@file";
service = "api@internal";
};
};
}];
# Combine the above 'core 'options with the (dynamicOptions)
# list of ingress routers for each serfie defined in various
# modules (cfg.routers)
# this folds the list and iterates each element to add them together
dynamicOptionsAttrset = lib.foldl' (acc: elem: lib.recursiveUpdate acc elem) { } (dynamicOptions ++ cfg.routers);
routersFile = builtins.toFile "routers.yaml" (builtins.toJSON dynamicOptionsAttrset);
in in
{ {
@ -21,185 +102,124 @@ in
}; };
config = mkIf cfg.enable { config = mkIf cfg.enable
{
networking.firewall.allowedTCPPorts = [ 80 443 ]; # put the dynamic configs in a file
# i put this in a file instead of piping directly into
# the traefik module, so that if i update the file
# with a new router nix doesnt restart traefik, it just updates
# the etc file and traefik picks up the changes.
environment.etc."traefik/config.yaml".source = routersFile;
sops.secrets."system/services/traefik/apiTokenFile".sopsFile = ./secrets.sops.yaml; networking.firewall.allowedTCPPorts = [ 80 443 ];
# Restart when secret changes sops.secrets."system/services/traefik/apiTokenFile".sopsFile = ./secrets.sops.yaml;
sops.secrets."system/services/traefik/apiTokenFile".restartUnits = [ "traefik.service" ];
systemd.services.traefik = { # Restart when secret changes
serviceConfig.EnvironmentFile = [ sops.secrets."system/services/traefik/apiTokenFile".restartUnits = [ "traefik.service" ];
config.sops.secrets."system/services/traefik/apiTokenFile".path
];
};
# add user to group to view files/storage systemd.services.traefik = {
users.users.truxnell.extraGroups = [ "traefik" ]; serviceConfig.EnvironmentFile = [
config.sops.secrets."system/services/traefik/apiTokenFile".path
];
};
services.traefik = { # add user to group to view files/storage
# TODO refactor into subfiles users.users.truxnell.extraGroups = [ "traefik" ];
enable = true;
group = "podman"; # podman backend, required to access socket
dataDir = "${config.mySystem.persistentFolder}/traefik"; services.traefik = {
# Required so traefik is permitted to watch docker events # TODO refactor into subfiles
# group = "docker"; enable = true;
group = "podman"; # podman backend, required to access socket
staticConfigOptions = { dataDir = "${config.mySystem.persistentFolder}/traefik";
# Required so traefik is permitted to watch docker events
# group = "docker";
global = { staticConfigOptions = {
checkNewVersion = false;
sendAnonymousUsage = false;
};
api.dashboard = true; global = {
log.level = "DEBUG"; checkNewVersion = false;
sendAnonymousUsage = false;
# Allow backend services to have self-signed certs
serversTransport.insecureSkipVerify = true;
providers = {
docker = {
endpoint = "unix:///var/run/podman/podman.sock";
exposedByDefault = false;
defaultRule = "Host(`{{ normalize .Name }}.${config.mySystem.domain}`)";
# network = "proxy";
};
file = {
filename = routersFile;
watch = true;
}; };
}; api.dashboard = true;
log.level = "DEBUG";
# Listen on port 80 and redirect to port 443 # Allow backend services to have self-signed certs
entryPoints.web = { serversTransport.insecureSkipVerify = true;
address = ":80";
http.redirections.entrypoint.to = "websecure";
};
# Run everything SSL providers = {
entryPoints.websecure = { docker = {
address = ":443"; endpoint = "unix:///var/run/podman/podman.sock";
http = { exposedByDefault = false;
tls = { defaultRule = "Host(`{{ normalize .Name }}.${config.mySystem.domain}`)";
certresolver = "letsencrypt"; # network = "proxy";
domains.main = "${config.mySystem.domain}"; };
domains.sans = "*.${config.mySystem.domain}"; file = {
filename = "/etc/traefik/config.yaml";
watch = true;
};
};
# Listen on port 80 and redirect to port 443
entryPoints.web = {
address = ":80";
http.redirections.entrypoint.to = "websecure";
};
# Run everything SSL
entryPoints.websecure = {
address = ":443";
http = {
tls = {
certresolver = "letsencrypt";
domains.main = "${config.mySystem.domain}";
domains.sans = "*.${config.mySystem.domain}";
};
};
http3 = { };
};
certificatesResolvers.letsencrypt.acme = {
dnsChallenge.provider = "cloudflare";
dnsChallenge.resolvers = [ "1.1.1.1:53" ];
keyType = "EC256";
storage = "${config.services.traefik.dataDir}/acme.json";
};
# };
};
# Dynamic configuration
# refer the etc file defined above with the build
# dynamic options
dynamicConfigFile = "/etc/traefik/config.yaml";
};
mySystem.services.homepage.infrastructure = [
{
"Traefik ${config.networking.hostName}" = {
icon = "traefik.png";
href = "https://traefik-${config.networking.hostName}.${config.mySystem.domain}/dashboard/";
description = "Reverse Proxy";
widget = {
type = "traefik";
url = "https://traefik-${config.networking.hostName}.${config.mySystem.domain}";
}; };
}; };
http3 = { }; }
}; ];
certificatesResolvers.letsencrypt.acme = { mySystem.services.gatus.monitors = [{
dnsChallenge.provider = "cloudflare";
dnsChallenge.resolvers = [ "1.1.1.1:53" ];
keyType = "EC256";
storage = "${config.services.traefik.dataDir}/acme.json";
};
# };
};
# Dynamic configuration
dynamicConfigOptions = {
http.middlewares = { name = "Traefik ${config.networking.hostName}";
# Whitelist local network and VPN addresses group = "infrastructure";
local-ip-only.ipWhiteList.sourceRange = [ url = "https://traefik-${config.networking.hostName}.${config.mySystem.domain}";
"127.0.0.1/32" # localhost interval = "1m";
"192.168.0.0/16" # RFC1918 conditions = [ "[CONNECTED] == true" "[STATUS] == 200" "[RESPONSE_TIME] < 50" ];
"10.0.0.0/8" # RFC1918 }];
"172.16.0.0/12" # RFC1918 (docker network)
];
# authelia = {
# # Forward requests w/ middlewares=authelia@file to authelia.
# forwardAuth = {
# # address = cfg.autheliaUrl;
# address = "http://localhost:9092/api/verify?rd=https://auth.dhupar.xyz:444/";
# trustForwardHeader = true;
# authResponseHeaders = [
# "Remote-User"
# "Remote-Name"
# "Remote-Email"
# "Remote-Groups"
# ];
# };
# };
# authelia-basic = {
# # Forward requests w/ middlewares=authelia-basic@file to authelia.
# forwardAuth = {
# address = "http://localhost:9092/api/verify?auth=basic";
# trustForwardHeader = true;
# authResponseHeaders = [
# "Remote-User"
# "Remote-Name"
# "Remote-Email"
# "Remote-Groups"
# ];
# };
# };
# https://oauth2-proxy.github.io/oauth2-proxy/docs/configuration/overview/#forwardauth-with-static-upstreams-configuration
# auth-headers = {
# browserXssFilter = true;
# contentTypeNosniff = true;
# forceSTSHeader = true;
# frameDeny = true;
# sslHost = domain;
# sslRedirect = true;
# stsIncludeSubdomains = true;
# stsPreload = true;
# stsSeconds = 315360000;
# };
};
tls.options.default = {
minVersion = "VersionTLS13";
sniStrict = true;
};
# Set up wildcard domain certificates for both *.hostname.domain and *.local.domain
http.routers = {
traefik = {
entrypoints = "websecure";
rule = "Host(`traefik-${config.networking.hostName}.${config.mySystem.domain}`)";
tls.certresolver = "letsencrypt";
tls.domains = [{
main = "${config.mySystem.domain}";
sans = "*.${config.mySystem.domain}";
}];
middlewares = "local-ip-only@file";
service = "api@internal";
};
};
};
}; };
mySystem.services.homepage.infrastructure = [
{
"Traefik ${config.networking.hostName}" = {
icon = "traefik.png";
href = "https://traefik-${config.networking.hostName}.${config.mySystem.domain}/dashboard/";
description = "Reverse Proxy";
widget = {
type = "traefik";
url = "https://traefik-${config.networking.hostName}.${config.mySystem.domain}";
};
};
}
];
mySystem.services.gatus.monitors = [{
name = "Traefik ${config.networking.hostName}";
group = "infrastructure";
url = "https://traefik-${config.networking.hostName}.${config.mySystem.domain}";
interval = "1m";
conditions = [ "[CONNECTED] == true" "[STATUS] == 200" "[RESPONSE_TIME] < 50" ];
}];
};
} }

View file

@ -0,0 +1,113 @@
{ lib
, config
, pkgs
, ...
}:
with lib;
let
cfg = config.mySystem.services.zigbee2mqtt;
persistentFolder = "${config.mySystem.persistentFolder}/nixos/services/${app}/";
app = "zigbee2mqtt";
user = app;
group = app;
in
{
options.mySystem.services.zigbee2mqtt = {
enable = mkEnableOption "zigbee2mqtt";
addToHomepage = mkEnableOption "Add ${app} to homepage" // { default = true; };
};
config = mkIf cfg.enable {
# ensure folder exist and has correct owner/group
systemd.tmpfiles.rules = [
"d ${persistentFolder} 0750 ${user} ${group} -" #The - disables automatic cleanup, so the file wont be removed after a period
];
sops.secrets."services/mosquitto/mq/plainPassword.yaml" = {
sopsFile = ../mosquitto/secrets.sops.yaml;
owner = config.users.users.zigbee2mqtt.name;
group = config.users.users.zigbee2mqtt.group;
restartUnits = [ "${app}.service" ];
};
services.zigbee2mqtt = {
enable = true;
dataDir = persistentFolder;
settings = {
advanced.log_level = "warn";
homeassistant = true;
permit_join = false;
include_device_information = true;
frontend =
{
port = 8080;
url = "https://${app}.${config.networking.domain}";
};
client_id = "z2m";
serial = {
port = "tcp://10.8.30.110:6638";
};
mqtt = {
server = "mqtt://mqtt.trux.dev:1883";
user = "mq";
password = "!${config.sops.secrets."services/mosquitto/mq/plainPassword.yaml".path} password";
};
};
};
users.users.truxnell.extraGroups = [ app ];
mySystem.services.traefik.routers = [{
http.routers.${app} = {
rule = "Host(`${app}.${config.mySystem.domain}`)";
entrypoints = "websecure";
middlewares = "local-ip-only@file";
service = "${app}";
};
http.services.${app} = {
loadBalancer = {
servers = [{
url = "http://localhost:8080";
}];
};
};
}];
mySystem.services.homepage.media = mkIf cfg.addToHomepage [
{
${app} = {
icon = "${app}.svg";
href = "https://${app}.${config.mySystem.domain}";
description = "Zigbee bridge to MQTT";
container = "${app}";
};
}
];
mySystem.services.gatus.monitors = [{
name = app;
group = "services";
url = "https://${app}.${config.mySystem.domain}";
interval = "1m";
conditions = [ "[CONNECTED] == true" "[STATUS] == 200" "[RESPONSE_TIME] < 50" ];
}];
services.restic.backups = config.lib.mySystem.mkRestic
{
inherit app;
user = builtins.toString user;
paths = [ persistentFolder ];
appFolder = app;
inherit persistentFolder;
};
};
}