nix-configs/modules/services/traefik.nix
Lucy Hochkamp 28dc0896b9
Some checks failed
ci/woodpecker/push/build-cache Pipeline failed
ci/woodpecker/cron/dependency-pr Pipeline was successful
navidrome
2025-12-04 00:21:41 +01:00

247 lines
6.4 KiB
Nix

{
pkgs,
lib,
config,
...
}:
let
inherit (lib)
mapAttrsToList
mkMerge
splitString
concatStringsSep
mkIf
mkOption
mkEnableOption
;
inherit (lib.types)
str
bool
nullOr
anything
attrsOf
submodule
listOf
;
cfg = config.xyno.services.traefik;
simpleProxyOpts = mapAttrsToList (
n: v:
let
router = v.routerName;
service = v.serviceName;
spl = splitString "." v.host;
certDomain = if (builtins.length spl) > 2 then concatStringsSep "." (builtins.tail spl) else spl;
in
mkMerge [
(mkIf v.robotProtection {
routers."${router}-robotstxt" = {
service = "robotstxt";
rule = "Host(`${v.host}`) && Path(`/robots.txt`)";
tls.certResolver = "letsencrypt";
tls.domains = [
{
main = certDomain;
sans = [ "*.${certDomain}" ];
}
];
};
services.robotstxt = {
loadBalancer.servers = [
{ url = "http://127.0.0.2:8080"; }
];
};
})
{
routers.${router} = {
inherit service;
inherit (v) middlewares;
rule = if v.rule != null then v.rule else "Host(`${v.host}`)";
tls.certResolver = "letsencrypt";
tls.domains = [
{
main = certDomain;
sans = [ "*.${certDomain}" ];
}
];
};
services.${service} = {
loadBalancer.servers = [
{ url = v.internal; }
];
loadBalancer.serversTransport = mkIf (v.transport != null) v.transport;
};
}
]
) cfg.simpleProxy;
in
{
options.xyno.services.traefik.enable = mkEnableOption "enables traefik";
options.xyno.services.traefik.noBots = mkOption {
type = bool;
default = true;
};
options.xyno.services.traefik.simpleProxy = mkOption {
example = {
"example" = {
host = "example.org";
middlewares = [ "meow" ];
internal = "http://127.0.0.1:8080";
};
};
default = { };
type = attrsOf (
submodule (
{ config, name,... }:
{
options = {
middlewares = mkOption {
type = listOf str;
default = [];
};
internal = mkOption {
type = str;
description = "where to proxy to";
};
host = mkOption {
type = str;
description = "used for the route and tls";
};
routerName = mkOption {
type = str;
default = "simpleproxy-${name}-router";
};
serviceName = mkOption {
type = str;
default = "simpleproxy-${name}-service";
};
robotProtection = mkOption {
type = bool;
default = true;
description = "robots.txt and (soon) iocane";
};
rule = mkOption {
type = str;
default = "Host(`${config.host}`)";
description = "overrides the Host(`\${host}`) rule with something custom if set";
};
transport = mkOption {
type = nullOr anything;
default = null;
};
};
}
)
);
};
config = mkIf cfg.enable {
services.nginx = {
enable = mkIf cfg.noBots true;
defaultListen = mkIf cfg.noBots [
{
addr = "127.0.0.2";
port = 8080;
}
];
virtualHosts._.default = true;
virtualHosts._.locations."/".root = pkgs.writeTextFile {
name = "robots.txt";
destination = "/robots.txt";
text = ''
User-agent: *
Disallow: /
'';
};
};
services.traefik = {
enable = true;
environmentFiles = [
config.sops.templates."traefik.env".path
];
staticConfigOptions = {
accessLog = {};
metrics = mkIf config.xyno.services.monitoring.enable {
otlp.http.endpoint = "http://localhost:8429/v1/metrics";
};
entryponits.web = {
address = ":80";
redirections.entryPoint = {
to = "websecure";
scheme = "https";
permanent = true;
};
};
entrypoints.websecure = {
address = ":443";
http.tls.certResolver = "letsencrypt";
http3 = { };
};
log.level = "INFO";
certificatesResolvers.letsencrypt.acme = {
email = "ssl@xyno.systems";
caServer = "https://acme-v02.api.letsencrypt.org/directory";
dnsChallenge = {
resolvers = [
"8.8.8.8"
"1.1.1.1"
];
provider = "desec";
};
};
};
dynamicConfigOptions = {
http = mkMerge simpleProxyOpts;
# tls.options.default = {
# # mozilla modern
# minVersion = "VersionTLS13";
# curvePreferences = [
# "X25519"
# "CurveP256"
# "CurveP384"
# ];
# };
# tls.options.old = {
# # mozilla intermediate
# minVersion = "VersionTLS12";
# curvePreferences = [
# "X25519"
# "CurveP256"
# "CurveP384"
# ];
# cipherSuites = [
# "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"
# "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
# "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384"
# "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"
# "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305"
# "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305"
# ];
# };
};
};
networking.firewall.allowedTCPPorts = [
80
443
];
networking.firewall.allowedUDPPorts = [ 443 ];
xyno.impermanence.directories = [ config.services.traefik.dataDir ];
sops.secrets."desec_token" = {
};
sops.templates."traefik.env".content = ''
DESEC_TOKEN=${config.sops.placeholder.desec_token}
DESEC_PROPAGATION_TIMEOUT=1200
LEGO_DISABLE_CNAME_SUPPORT=true
'';
sops.templates."traefik.env".reloadUnits = [ "traefik.service" ];
# services.borgmatic.settings.traefikql_databases = [
# {
# name = "all"; # gets run as root anyways so can log in
# }
# ];
};
}