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;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -7,103 +7,10 @@
with lib;
let
cfg = config.mySystem.services.traefik;
routersFile = builtins.toFile "routers.yaml" (builtins.toJSON cfg.routers);
in
{
options.mySystem.services.traefik = {
enable = mkEnableOption "Traefik reverse proxy";
routers = lib.mkOption {
type = lib.types.listOf lib.types.attrs;
description = "Routers to add to traefik";
default = [ ];
};
};
config = mkIf cfg.enable {
networking.firewall.allowedTCPPorts = [ 80 443 ];
sops.secrets."system/services/traefik/apiTokenFile".sopsFile = ./secrets.sops.yaml;
# Restart when secret changes
sops.secrets."system/services/traefik/apiTokenFile".restartUnits = [ "traefik.service" ];
systemd.services.traefik = {
serviceConfig.EnvironmentFile = [
config.sops.secrets."system/services/traefik/apiTokenFile".path
];
};
# add user to group to view files/storage
users.users.truxnell.extraGroups = [ "traefik" ];
services.traefik = {
# TODO refactor into subfiles
enable = true;
group = "podman"; # podman backend, required to access socket
dataDir = "${config.mySystem.persistentFolder}/traefik";
# Required so traefik is permitted to watch docker events
# group = "docker";
staticConfigOptions = {
global = {
checkNewVersion = false;
sendAnonymousUsage = false;
};
api.dashboard = true;
log.level = "DEBUG";
# 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;
};
};
# 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
dynamicConfigOptions = {
# core dynamic options to define middleware
# sso etc
dynamicOptions = [{
http.middlewares = {
# Whitelist local network and VPN addresses
@ -174,9 +81,122 @@ in
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
{
options.mySystem.services.traefik = {
enable = mkEnableOption "Traefik reverse proxy";
routers = lib.mkOption {
type = lib.types.listOf lib.types.attrs;
description = "Routers to add to traefik";
default = [ ];
};
};
config = mkIf cfg.enable
{
# 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;
networking.firewall.allowedTCPPorts = [ 80 443 ];
sops.secrets."system/services/traefik/apiTokenFile".sopsFile = ./secrets.sops.yaml;
# Restart when secret changes
sops.secrets."system/services/traefik/apiTokenFile".restartUnits = [ "traefik.service" ];
systemd.services.traefik = {
serviceConfig.EnvironmentFile = [
config.sops.secrets."system/services/traefik/apiTokenFile".path
];
};
# add user to group to view files/storage
users.users.truxnell.extraGroups = [ "traefik" ];
services.traefik = {
# TODO refactor into subfiles
enable = true;
group = "podman"; # podman backend, required to access socket
dataDir = "${config.mySystem.persistentFolder}/traefik";
# Required so traefik is permitted to watch docker events
# group = "docker";
staticConfigOptions = {
global = {
checkNewVersion = false;
sendAnonymousUsage = false;
};
api.dashboard = true;
log.level = "DEBUG";
# 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 = "/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}" = {

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