refactor: abstract traefik router labels and dynamic config
This commit is contained in:
parent
d742671460
commit
95e67dd151
9 changed files with 152 additions and 74 deletions
|
|
@ -110,6 +110,29 @@ in {
|
||||||
sonarr = lib.mkIf cfg.sonarr.enable (mkUser 3007);
|
sonarr = lib.mkIf cfg.sonarr.enable (mkUser 3007);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
homelab.traefikRouters = {
|
||||||
|
bazarr = lib.mkIf cfg.bazarr.enable {
|
||||||
|
rule = "Host(`bazarr.depeuter.dev`)";
|
||||||
|
port = 6767;
|
||||||
|
};
|
||||||
|
prowlarr = lib.mkIf cfg.prowlarr.enable {
|
||||||
|
rule = "Host(`prowlarr.depeuter.dev`)";
|
||||||
|
port = 9696;
|
||||||
|
};
|
||||||
|
qbittorrent = lib.mkIf cfg.qbittorrent.enable {
|
||||||
|
rule = "Host(`qb.depeuter.dev`)";
|
||||||
|
port = 10095;
|
||||||
|
};
|
||||||
|
radarr = lib.mkIf cfg.radarr.enable {
|
||||||
|
rule = "Host(`radarr.depeuter.dev`)";
|
||||||
|
port = 7878;
|
||||||
|
};
|
||||||
|
sonarr = lib.mkIf cfg.sonarr.enable {
|
||||||
|
rule = "Host(`sonarr.depeuter.dev`)";
|
||||||
|
port = 8989;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
virtualisation.oci-containers.containers = let
|
virtualisation.oci-containers.containers = let
|
||||||
videoHostPath = config.homelab.fileSystems.media.video.hostPath;
|
videoHostPath = config.homelab.fileSystems.media.video.hostPath;
|
||||||
in {
|
in {
|
||||||
|
|
@ -141,12 +164,6 @@ in {
|
||||||
"${videoHostPath}/Films:/media/movies"
|
"${videoHostPath}/Films:/media/movies"
|
||||||
"${videoHostPath}/Series:/media/series"
|
"${videoHostPath}/Series:/media/series"
|
||||||
];
|
];
|
||||||
labels = {
|
|
||||||
"traefik.enable" = "true";
|
|
||||||
"traefik.docker.network" = proxyNet;
|
|
||||||
"traefik.http.routers.bazarr.rule" = "Host(`bazarr.depeuter.dev`)";
|
|
||||||
"traefik.http.services.bazarr.loadbalancer.server.port" = toString port;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
prowlarr = let
|
prowlarr = let
|
||||||
|
|
@ -172,12 +189,6 @@ in {
|
||||||
|
|
||||||
"/srv/prowlarr-backup:/config/Backups"
|
"/srv/prowlarr-backup:/config/Backups"
|
||||||
];
|
];
|
||||||
labels = {
|
|
||||||
"traefik.enable" = "true";
|
|
||||||
"traefik.docker.network" = proxyNet;
|
|
||||||
"traefik.http.routers.prowlarr.rule" = "Host(`prowlarr.depeuter.dev`)";
|
|
||||||
"traefik.http.services.prowlarr.loadbalancer.server.port" = toString port;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
qbittorrent = let
|
qbittorrent = let
|
||||||
|
|
@ -205,12 +216,6 @@ in {
|
||||||
|
|
||||||
"/srv/torrent:/media/cache"
|
"/srv/torrent:/media/cache"
|
||||||
];
|
];
|
||||||
labels = {
|
|
||||||
"traefik.enable" = "true";
|
|
||||||
"traefik.docker.network" = proxyNet;
|
|
||||||
"traefik.http.routers.qbittorrent.rule" = "Host(`qb.depeuter.dev`)";
|
|
||||||
"traefik.http.services.qbittorrent.loadbalancer.server.port" = toString port;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
radarr = let
|
radarr = let
|
||||||
|
|
@ -239,12 +244,6 @@ in {
|
||||||
"/srv/torrent:/media/cache"
|
"/srv/torrent:/media/cache"
|
||||||
"${videoHostPath}/Films:/media/movies"
|
"${videoHostPath}/Films:/media/movies"
|
||||||
];
|
];
|
||||||
labels = {
|
|
||||||
"traefik.enable" = "true";
|
|
||||||
"traefik.docker.network" = proxyNet;
|
|
||||||
"traefik.http.routers.radarr.rule" = "Host(`radarr.depeuter.dev`)";
|
|
||||||
"traefik.http.services.radarr.loadbalancer.server.port" = toString port;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
sonarr = let
|
sonarr = let
|
||||||
|
|
@ -273,12 +272,6 @@ in {
|
||||||
"/srv/torrent:/media/cache"
|
"/srv/torrent:/media/cache"
|
||||||
"${videoHostPath}/Series:/media/series"
|
"${videoHostPath}/Series:/media/series"
|
||||||
];
|
];
|
||||||
labels = {
|
|
||||||
"traefik.enable" = "true";
|
|
||||||
"traefik.docker.network" = proxyNet;
|
|
||||||
"traefik.http.routers.sonarr.rule" = "Host(`sonarr.depeuter.dev`)";
|
|
||||||
"traefik.http.services.sonarr.loadbalancer.server.port" = toString port;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,16 @@ in {
|
||||||
"docker-calibre.service"
|
"docker-calibre.service"
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
homelab.traefikRouters = {
|
||||||
|
calibre = lib.mkIf cfg.desktop.enable {
|
||||||
|
rule = "Host(`calibre.depeuter.dev`)";
|
||||||
|
port = 8080;
|
||||||
|
};
|
||||||
|
calibre-web = lib.mkIf cfg.web.enable {
|
||||||
|
rule = "Host(`books.depeuter.dev`)";
|
||||||
|
port = 8083;
|
||||||
|
};
|
||||||
|
};
|
||||||
})
|
})
|
||||||
|
|
||||||
# Calibre desktop
|
# Calibre desktop
|
||||||
|
|
@ -102,12 +112,6 @@ in {
|
||||||
|
|
||||||
"${books}:/media/books"
|
"${books}:/media/books"
|
||||||
];
|
];
|
||||||
labels = {
|
|
||||||
"traefik.enable" = "true";
|
|
||||||
"traefik.docker.network" = proxyNet;
|
|
||||||
"traefik.http.routers.calibre.rule" = "Host(`calibre.depeuter.dev`)";
|
|
||||||
"traefik.http.services.calibre.loadbalancer.server.port" = toString innerPort;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -153,12 +157,6 @@ in {
|
||||||
|
|
||||||
"${books}:/media/books"
|
"${books}:/media/books"
|
||||||
];
|
];
|
||||||
labels = {
|
|
||||||
"traefik.enable" = "true";
|
|
||||||
"traefik.docker.network" = proxyNet;
|
|
||||||
"traefik.http.routers.calibre-web.rule" = "Host(`books.depeuter.dev`)";
|
|
||||||
"traefik.http.services.calibre-web.loadbalancer.server.port" = toString innerPort;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ let
|
||||||
cfg = config.homelab.apps.freshrss;
|
cfg = config.homelab.apps.freshrss;
|
||||||
|
|
||||||
networkName = "freshrss";
|
networkName = "freshrss";
|
||||||
|
proxyNet = config.homelab.apps.traefik.sharedNetworkName;
|
||||||
in {
|
in {
|
||||||
options.homelab.apps.freshrss = {
|
options.homelab.apps.freshrss = {
|
||||||
enable = lib.mkEnableOption "FreshRSS";
|
enable = lib.mkEnableOption "FreshRSS";
|
||||||
|
|
@ -36,6 +37,22 @@ in {
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
homelab.traefikRouters.freshrss = {
|
||||||
|
rule = "Host(`rss.depeuter.dev`)";
|
||||||
|
port = 80;
|
||||||
|
tls = true;
|
||||||
|
entryPoints = [ "websecure" ];
|
||||||
|
middlewares = [ "freshrssM1" "freshrssM2" ];
|
||||||
|
extraLabels = {
|
||||||
|
"traefik.http.middlewares.freshrssM1.compress" = "true";
|
||||||
|
"traefik.http.middlewares.freshrssM2.headers.browserXssFilter" = "true";
|
||||||
|
"traefik.http.middlewares.freshrssM2.headers.forceSTSHeader" = "true";
|
||||||
|
"traefik.http.middlewares.freshrssM2.headers.frameDeny" = "true";
|
||||||
|
"traefik.http.middlewares.freshrssM2.headers.referrerPolicy" = "no-referrer-when-downgrade";
|
||||||
|
"traefik.http.middlewares.freshrssM2.headers.stsSeconds" = "31536000";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
virtualisation.oci-containers.containers.freshrss = {
|
virtualisation.oci-containers.containers.freshrss = {
|
||||||
hostname = "freshrss";
|
hostname = "freshrss";
|
||||||
image = "freshrss/freshrss:1.25.0";
|
image = "freshrss/freshrss:1.25.0";
|
||||||
|
|
@ -46,6 +63,7 @@ in {
|
||||||
];
|
];
|
||||||
extraOptions = [
|
extraOptions = [
|
||||||
"--network=${networkName}"
|
"--network=${networkName}"
|
||||||
|
"--network=${proxyNet}"
|
||||||
];
|
];
|
||||||
environment = {
|
environment = {
|
||||||
TZ = config.time.timeZone;
|
TZ = config.time.timeZone;
|
||||||
|
|
@ -57,22 +75,6 @@ in {
|
||||||
"/srv/freshrss/www/freshrss/data:/var/www/FreshRSS/data"
|
"/srv/freshrss/www/freshrss/data:/var/www/FreshRSS/data"
|
||||||
"/srv/freshrss/www/freshrss/extensions:/var/www/FreshRSS/extensions"
|
"/srv/freshrss/www/freshrss/extensions:/var/www/FreshRSS/extensions"
|
||||||
];
|
];
|
||||||
labels = {
|
|
||||||
"traefik.enable" = "true";
|
|
||||||
|
|
||||||
"traefik.http.middlewares.freshrssM1.compress" = "true";
|
|
||||||
"traefik.http.middlewares.freshrssM2.headers.browserXssFilter" = "true";
|
|
||||||
"traefik.http.middlewares.freshrssM2.headers.forceSTSHeader" = "true";
|
|
||||||
"traefik.http.middlewares.freshrssM2.headers.frameDeny" = "true";
|
|
||||||
"traefik.http.middlewares.freshrssM2.headers.referrerPolicy" = "no-referrer-when-downgrade";
|
|
||||||
"traefik.http.middlewares.freshrssM2.headers.stsSeconds" = "31536000";
|
|
||||||
"traefik.http.routers.freshrss.entryPoints" = "websecure";
|
|
||||||
"traefik.http.routers.freshrss.tls" = "true";
|
|
||||||
|
|
||||||
"traefik.http.services.freshrss.loadbalancer.server.port" = "80";
|
|
||||||
"traefik.http.routers.freshrss.middlewares" = "freshrssM1,freshrssM2";
|
|
||||||
"traefik.http.routers.freshrss.rule" = "Host(`rss.depeuter.dev`)";
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,13 @@ in {
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
homelab.traefikRouters.homepage = let
|
||||||
|
host = "homepage.${config.networking.domain}";
|
||||||
|
in {
|
||||||
|
rule = "Host(`${host}`)";
|
||||||
|
port = cfg.port;
|
||||||
|
};
|
||||||
|
|
||||||
virtualisation.oci-containers.containers.homepage = let
|
virtualisation.oci-containers.containers.homepage = let
|
||||||
host = "homepage.${config.networking.domain}";
|
host = "homepage.${config.networking.domain}";
|
||||||
in {
|
in {
|
||||||
|
|
@ -54,12 +61,6 @@ in {
|
||||||
"${homepage-config}:/app/config"
|
"${homepage-config}:/app/config"
|
||||||
# "/var/run/docker.sock:/var/run/docker.sock:ro" # For docker integrations
|
# "/var/run/docker.sock:/var/run/docker.sock:ro" # For docker integrations
|
||||||
];
|
];
|
||||||
labels = {
|
|
||||||
"traefik.enable" = "true";
|
|
||||||
"traefik.docker.network" = proxyNet;
|
|
||||||
"traefik.http.routers.homepage.rule" = "Host(`${host}`)";
|
|
||||||
"traefik.http.services.homepage.loadbalancer.server.port" = toString cfg.port;
|
|
||||||
};
|
|
||||||
environment = {
|
environment = {
|
||||||
inherit PUID PGID;
|
inherit PUID PGID;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ let
|
||||||
cfg = config.homelab.apps.jellyfin;
|
cfg = config.homelab.apps.jellyfin;
|
||||||
|
|
||||||
networkName = "jellyfin";
|
networkName = "jellyfin";
|
||||||
|
proxyNet = config.homelab.apps.traefik.sharedNetworkName;
|
||||||
inherit (config.homelab.fileSystems) media;
|
inherit (config.homelab.fileSystems) media;
|
||||||
|
|
||||||
UID = 3008;
|
UID = 3008;
|
||||||
|
|
@ -49,6 +50,14 @@ in {
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
homelab.traefikRouters.feishin = {
|
||||||
|
rule = "Host(`play.jelly.depeuter.dev`)";
|
||||||
|
port = 9180;
|
||||||
|
extraLabels = {
|
||||||
|
"traefik.tls.options.default.minVersion" = "VersionTLS13";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
virtualisation.oci-containers.containers = {
|
virtualisation.oci-containers.containers = {
|
||||||
jellyfin = {
|
jellyfin = {
|
||||||
hostname = "jellyfin";
|
hostname = "jellyfin";
|
||||||
|
|
@ -102,6 +111,7 @@ in {
|
||||||
];
|
];
|
||||||
extraOptions = [
|
extraOptions = [
|
||||||
"--network=${networkName}"
|
"--network=${networkName}"
|
||||||
|
"--network=${proxyNet}"
|
||||||
];
|
];
|
||||||
environment = {
|
environment = {
|
||||||
# pre defined server name
|
# pre defined server name
|
||||||
|
|
@ -114,12 +124,6 @@ in {
|
||||||
SERVER_URL= "https://jelly.depeuter.dev";
|
SERVER_URL= "https://jelly.depeuter.dev";
|
||||||
TZ = config.time.timeZone;
|
TZ = config.time.timeZone;
|
||||||
};
|
};
|
||||||
labels = {
|
|
||||||
"traefik.enable" = "true";
|
|
||||||
"traefik.http.routers.feishin.rule" = "Host(`play.jelly.depeuter.dev`)";
|
|
||||||
"traefik.http.services.feishin.loadbalancer.server.port" = feishinPort;
|
|
||||||
"traefik.tls.options.default.minVersion" = "VersionTLS13";
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -136,7 +136,7 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
# 6. Traefik Reverse Proxy for Grafana and NTFY
|
# 6. Traefik Reverse Proxy for Grafana and NTFY
|
||||||
services.traefik.dynamicConfigOptions.http = {
|
homelab.apps.traefik.dynamicConfigOptions.http = {
|
||||||
routers = {
|
routers = {
|
||||||
grafana = {
|
grafana = {
|
||||||
rule = "Host(`grafana.lab.depeuter.dev`)";
|
rule = "Host(`grafana.lab.depeuter.dev`)";
|
||||||
|
|
@ -148,8 +148,8 @@
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
services = {
|
services = {
|
||||||
grafana.loadBalancer.servers = [{ url = "http://127.0.0.1:3000"; }];
|
grafana.loadBalancer.servers = [{ url = "http://host.docker.internal:3000"; }];
|
||||||
ntfy.loadBalancer.servers = [{ url = "http://127.0.0.1:2586"; }];
|
ntfy.loadBalancer.servers = [{ url = "http://host.docker.internal:2586"; }];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,11 @@ in {
|
||||||
default = "traefik";
|
default = "traefik";
|
||||||
description = "The name of the shared network to connect the container to.";
|
description = "The name of the shared network to connect the container to.";
|
||||||
};
|
};
|
||||||
|
dynamicConfigOptions = lib.mkOption {
|
||||||
|
type = lib.types.attrs;
|
||||||
|
default = {};
|
||||||
|
description = "Dynamic configuration options to write to file and mount into Traefik.";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
config = lib.mkIf cfg.enable {
|
config = lib.mkIf cfg.enable {
|
||||||
|
|
@ -35,6 +40,7 @@ in {
|
||||||
];
|
];
|
||||||
extraOptions = [
|
extraOptions = [
|
||||||
"--network=${cfg.sharedNetworkName}"
|
"--network=${cfg.sharedNetworkName}"
|
||||||
|
"--add-host=host.docker.internal:host-gateway"
|
||||||
];
|
];
|
||||||
environmentFiles = [
|
environmentFiles = [
|
||||||
/home/admin/.cloudflare.secret
|
/home/admin/.cloudflare.secret
|
||||||
|
|
@ -46,6 +52,10 @@ in {
|
||||||
"--providers.docker=true"
|
"--providers.docker=true"
|
||||||
"--providers.docker.exposedByDefault=false"
|
"--providers.docker.exposedByDefault=false"
|
||||||
|
|
||||||
|
# Add File provider
|
||||||
|
"--providers.file.filename=/etc/traefik/dynamic_conf.yml"
|
||||||
|
"--providers.file.watch=true"
|
||||||
|
|
||||||
# Add web entrypoint
|
# Add web entrypoint
|
||||||
"--entrypoints.web.address=:80/tcp"
|
"--entrypoints.web.address=:80/tcp"
|
||||||
"--entrypoints.web.http.redirections.entrypoint.to=websecure"
|
"--entrypoints.web.http.redirections.entrypoint.to=websecure"
|
||||||
|
|
@ -65,10 +75,14 @@ in {
|
||||||
"--certificatesresolvers.letsencrypt.acme.email=tibo.depeuter@telenet.be"
|
"--certificatesresolvers.letsencrypt.acme.email=tibo.depeuter@telenet.be"
|
||||||
"--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
|
"--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
|
||||||
];
|
];
|
||||||
volumes = [
|
volumes = let
|
||||||
|
dynamicConfFormat = pkgs.formats.yaml { };
|
||||||
|
dynamicConfFile = dynamicConfFormat.generate "traefik-dynamic-conf.yml" cfg.dynamicConfigOptions;
|
||||||
|
in [
|
||||||
"letsencryp:/letsencrypt"
|
"letsencryp:/letsencrypt"
|
||||||
|
|
||||||
"/var/run/docker.sock:/var/run/docker.sock:ro"
|
"/var/run/docker.sock:/var/run/docker.sock:ro"
|
||||||
|
"${dynamicConfFile}:/etc/traefik/dynamic_conf.yml:ro"
|
||||||
];
|
];
|
||||||
labels = {
|
labels = {
|
||||||
"traefik.enable" = "true";
|
"traefik.enable" = "true";
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
./docker.nix
|
./docker.nix
|
||||||
./monitoring.nix
|
./monitoring.nix
|
||||||
./nfs.nix
|
./nfs.nix
|
||||||
|
./traefik.nix
|
||||||
./users.nix
|
./users.nix
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
||||||
65
modules/common/traefik.nix
Normal file
65
modules/common/traefik.nix
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
{ config, lib, ... }:
|
||||||
|
|
||||||
|
let
|
||||||
|
proxyNet = config.homelab.apps.traefik.sharedNetworkName;
|
||||||
|
in {
|
||||||
|
options.homelab.traefikRouters = lib.mkOption {
|
||||||
|
type = lib.types.attrsOf (lib.types.submodule {
|
||||||
|
options = {
|
||||||
|
rule = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
description = "The Traefik router rule, e.g. Host(`example.com`)";
|
||||||
|
};
|
||||||
|
port = lib.mkOption {
|
||||||
|
type = lib.types.int;
|
||||||
|
description = "The port the service listens on";
|
||||||
|
};
|
||||||
|
middlewares = lib.mkOption {
|
||||||
|
type = lib.types.listOf lib.types.str;
|
||||||
|
default = [];
|
||||||
|
description = "Middlewares to apply";
|
||||||
|
};
|
||||||
|
tls = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = false;
|
||||||
|
description = "Whether to enable TLS";
|
||||||
|
};
|
||||||
|
entryPoints = lib.mkOption {
|
||||||
|
type = lib.types.listOf lib.types.str;
|
||||||
|
default = [];
|
||||||
|
description = "Entrypoints to use";
|
||||||
|
};
|
||||||
|
extraLabels = lib.mkOption {
|
||||||
|
type = lib.types.attrsOf lib.types.str;
|
||||||
|
default = {};
|
||||||
|
description = "Extra labels to apply";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
});
|
||||||
|
default = {};
|
||||||
|
description = "Declarative Traefik router configuration";
|
||||||
|
};
|
||||||
|
|
||||||
|
config = {
|
||||||
|
# Generate labels for containers based on homelab.traefikRouters
|
||||||
|
# This assumes that the name in homelab.traefikRouters matches the container name.
|
||||||
|
virtualisation.oci-containers.containers = lib.mapAttrs (name: router: {
|
||||||
|
labels = {
|
||||||
|
"traefik.enable" = "true";
|
||||||
|
"traefik.docker.network" = proxyNet;
|
||||||
|
"traefik.http.routers.${name}.rule" = router.rule;
|
||||||
|
"traefik.http.services.${name}.loadbalancer.server.port" = toString router.port;
|
||||||
|
}
|
||||||
|
// lib.optionalAttrs (router.middlewares != []) {
|
||||||
|
"traefik.http.routers.${name}.middlewares" = builtins.concatStringsSep "," router.middlewares;
|
||||||
|
}
|
||||||
|
// lib.optionalAttrs router.tls {
|
||||||
|
"traefik.http.routers.${name}.tls" = "true";
|
||||||
|
}
|
||||||
|
// lib.optionalAttrs (router.entryPoints != []) {
|
||||||
|
"traefik.http.routers.${name}.entryPoints" = builtins.concatStringsSep "," router.entryPoints;
|
||||||
|
}
|
||||||
|
// router.extraLabels;
|
||||||
|
}) config.homelab.traefikRouters;
|
||||||
|
};
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue