{ config, pkgs, ... }: with lib; let cfg = config.numbus.services.traefik; containerName = "traefik"; pod = "false"; composeFile = "podman/traefik/compose.yaml"; in { options.numbus.services.traefik = { enable = mkOption { type = lib.types.bool; default = true; example = true; description = "Traefik reverse-proxy"; }; staticConfigFile = mkOption { type = types.str; default = "traefik/config.yaml"; example = "traefik/config.yaml"; description = "The directory path where Traefik's static configuration file will be stored, prefixed by /etc/"; }; dataDir = mkOption { type = types.str; default = "/mnt/config/traefik"; example = "/mnt/config/traefik"; description = "The directory where traefik's data (i.e. SSL certificates) will be stored"; }; subdomain = mkOption { type = types.str; default = "traefik"; example = "traefik"; description = "The subdomain that traefik will use (i.e. your-subdomain.your-domain.com)"; }; logLevel = mkOption { type = types.enum [ "TRACE" "DEBUG" "INFO" "WARN" "ERROR" "FATAL" ]; default = "ERROR"; example = "ERROR"; description = "The level of detail Traefik should print in the logs : TRACE, DEBUG, INFO, WARN, ERROR, FATAL (from most to least verbose)"; }; }; config = mkIf cfg.enable { environment.etc."${composeFile}".text = /* yaml */ '' services: traefik: image: docker.io/library/traefik:latest container_name: traefik hostname: traefik network_mode: pasta ports: - "80:80/tcp" - "443:443/tcp" volumes: - /run/user/1000/podman/podman.sock:/run/docker.sock:ro - ${cfg.staticConfigFile}:/etc/traefik/traefik.yaml:ro - ${config.numbus.services.traefikDynamicConfigDir}:/etc/traefik/conf:ro - ${cfg.dataDir}:/var/traefik/certs:rw environment: - CF_DNS_API_TOKEN=$CF_DNS_API_TOKEN security_opt: - no-new-privileges:true restart: unless-stopped ''; environment.etc."${cfg.staticConfigFile}".text = /* yaml */ '' global: checkNewVersion: false sendAnonymousUsage: false log: level: ${cfg.logLevel} accesslog: {} api: dashboard: false insecure: false entryPoints: web: address: :80 http: redirections: entryPoint: to: websecure scheme: https websecure: address: :443 forwardedHeaders: trustedIPs: - "127.0.0.1/32" - "10.0.0.0/8" - "192.168.0.0/16" - "172.16.0.0/12" certificatesResolvers: cloudflare: acme: email: ${config.numbus.email.administratorEmail} storage: /var/traefik/certs/cloudflare-acme.json caServer: "https://acme-v02.api.letsencrypt.org/directory" dnsChallenge: provider: cloudflare resolvers: - "1.1.1.1:53" - "9.9.9.9:53" serversTransport: insecureSkipVerify: true providers: file: directory: "/etc/traefik/conf/" watch: true ''; environment.etc."${config.numbus.services.traefikDynamicConfigDir}/secureHeaders.yaml".text = /* yaml */ '' http: middlewares: secureHeaders: headers: FrameDeny: true AccessControlAllowMethods: 'GET,OPTIONS,PUT' AccessControlAllowOriginList: - origin-list-or-null AccessControlMaxAge: 100 AddVaryHeader: true BrowserXssFilter: true ContentTypeNosniff: true ForceSTSHeader: true STSIncludeSubdomains: true STSPreload: true ContentSecurityPolicy: default-src 'self' 'unsafe-inline' CustomFrameOptionsValue: SAMEORIGIN ReferrerPolicy: same-origin PermissionsPolicy: vibrate 'self' STSSeconds: 315360000 ''; environment.etc."${config.numbus.services.traefikDynamicConfigDir}/secureTLS.yaml".text = /* yaml */ '' tls: options: secureTLS: minVersion: VersionTLS12 sniStrict: true curvePreferences: - CurveP521 - CurveP384 cipherSuites: - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 - TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 ''; systemd.services."${containerName}" = { description = "Podman container : ${containerName}"; requires = [ "network.target" ]; after = [ "network.target" ]; wantedBy = [ "multi-user.target" ]; path = [ pkgs.podman pkgs.podman-compose pkgs.coreutils pkgs.sudo ]; serviceConfig = { Type = "exec"; ExecStartPre = "bash -c 'sleep $((RANDOM % 180))'"; ExecStart = "sudo -u numbus-admin podman-compose --in-pod ${pod} -f /etc/${composeFile} up --remove-orphans"; ExecStop = "sudo -u numbus-admin podman-compose --in-pod ${pod} -f /etc/${composeFile} down"; Restart = "on-failure"; RestartSec = "1m"; StartLimitBurst = "5"; }; }; systemd.services."update-${containerName}" = { description = "Update ${containerName} container"; path = [ pkgs.podman pkgs.podman-compose pkgs.sudo ]; serviceConfig = { Type = "oneshot"; ExecStart = [ "sudo -u numbus-admin podman-compose --in-pod ${pod} -f /etc/${composeFile} pull" "sudo -u numbus-admin podman-compose --in-pod ${pod} -f /etc/${composeFile} down" "sudo -u numbus-admin podman-compose --in-pod ${pod} -f /etc/${composeFile} up -d" ]; }; }; systemd.timers."update-${containerName}" = { timerConfig = { OnCalendar = "02:00"; RandomizedDelaySec = "60m"; Unit = "update-${containerName}.service"; }; wantedBy = [ "timers.target" ]; }; }; }