diff --git a/hosts/caddy/default.nix b/hosts/caddy/default.nix index a56a28b..5e114b0 100644 --- a/hosts/caddy/default.nix +++ b/hosts/caddy/default.nix @@ -6,11 +6,20 @@ }: let tailscaleMagicDNS = "neon-dory.ts.net"; + publicDomain = "pazpi.top"; + tsDomain = "tegola.pro"; + email = "pasettodavide@gmail.com"; in { age.secrets = { + searx-secret.file = ../../secrets/searx-secret.age; tailscale-authKey.file = ../../secrets/tailscale-authKey.age; + cloudflare-tegola-apiKey = { + file = ../../secrets/cloudflare-tegola-apiKey.age; + owner = config.services.caddy.user; + group = config.services.caddy.group; + }; }; my = { @@ -18,10 +27,20 @@ in services = { + dashy = { + enable = true; + settings = import ./dashy-settings.nix; + proxy = { + enable = true; + domain = tsDomain; + host = "caddy.internal"; + }; + }; + media-mgr = { proxy = { enable = true; - domain = "tegola.pro"; + domain = tsDomain; host = "arr.internal"; }; }; @@ -29,15 +48,25 @@ in nextcloud = { proxy = { enable = true; - domain = "tegola.pro"; + domain = tsDomain; host = "nextcloud.internal"; }; }; + searx = { + enable = true; + secretFile = config.age.secrets.searx-secret.path; + proxy = { + enable = true; + domain = tsDomain; + host = "caddy.internal"; + }; + }; + vaultwarden = { proxy = { enable = true; - domain = "tegola.pro"; + domain = tsDomain; host = "vaultwarden.internal"; }; @@ -48,7 +77,7 @@ in prometheus = { proxy = { enable = true; - domain = "tegola.pro"; + domain = tsDomain; host = "metrics.internal"; }; }; @@ -56,7 +85,7 @@ in grafana = { proxy = { enable = true; - domain = "tegola.pro"; + domain = tsDomain; host = "metrics.internal"; }; }; @@ -71,14 +100,27 @@ in caddy = { enable = true; - email = "pasettodavide@gmail.com"; - domain = "tegola.pro"; + domainsList = [ + { + domain = tsDomain; + email = email; + cloudflareApiKeyFile = config.age.secrets.cloudflare-tegola-apiKey.path; + } + ]; + # email = "pasettodavide@gmail.com"; + # domain = tsDomain; + # claudflareApiKeyFile = config.age.secrets.cloudflare-tegola-apiKey.path; }; }; virtualisation = { proxmox.enable = true; + portainer.proxy = { + enable = true; + domain = tsDomain; + host = "portainer.internal"; + }; }; }; diff --git a/hosts/metrics/default.nix b/hosts/metrics/default.nix index cef04ab..dedf455 100644 --- a/hosts/metrics/default.nix +++ b/hosts/metrics/default.nix @@ -11,6 +11,10 @@ in age.secrets = { tailscale-authKey.file = ../../secrets/tailscale-authKey.age; + grafana-admin-pwd = { + file = ../../secrets/grafana-admin-pwd.age; + owner = "grafana"; + }; }; my = { @@ -21,7 +25,10 @@ in }; monitoring = { - grafana.enable = true; + grafana = { + enable = true; + adminPasswordFile = config.age.secrets.grafana-admin-pwd.path; + }; prometheus.enable = true; loki.enable = true; }; diff --git a/hosts/nextcloud/default.nix b/hosts/nextcloud/default.nix index 4fb05a7..608ae09 100644 --- a/hosts/nextcloud/default.nix +++ b/hosts/nextcloud/default.nix @@ -5,11 +5,22 @@ ... }: { + + age.secrets = { + nextcloud-admin-pwd = { + file = ../../secrets/nextcloud-admin-pwd.age; + owner = "nextcloud"; + group = "nextcloud"; + mode = "770"; + }; + }; + my = { utils.commons.enable = true; services.nextcloud = { enable = true; + adminPasswordFile = config.age.secrets.nextcloud-admin-pwd.path; proxy.domain = "tegola.pro"; }; diff --git a/hosts/vaultwarden/default.nix b/hosts/vaultwarden/default.nix index deb64d1..b35e4b7 100644 --- a/hosts/vaultwarden/default.nix +++ b/hosts/vaultwarden/default.nix @@ -5,10 +5,14 @@ ... }: { + + age.secrets.vaultwarden-admin-pwd.file = ../../secrets/vaultwarden-admin-pwd.age; + my = { utils.commons.enable = true; services.vaultwarden = { enable = true; + adminPasswordFile = config.age.secrets.vaultwarden-admin-pwd.path; proxy.domain = "tegola.pro"; }; virtualisation.proxmox.enable = true; diff --git a/modules/monitoring/grafana.nix b/modules/monitoring/grafana.nix index 293eb0d..3a6d3ee 100644 --- a/modules/monitoring/grafana.nix +++ b/modules/monitoring/grafana.nix @@ -15,6 +15,14 @@ in options.my.monitoring.grafana = { enable = lib.mkEnableOption "Enable grafana as a data visualization"; + adminPasswordFile = lib.mkOption { + default = ""; + type = lib.types.str; + description = '' + Path to the file containing the admin password for Grafana + ''; + }; + proxy = { enable = lib.mkEnableOption "Set the proxy entry for this service"; @@ -26,6 +34,14 @@ in ''; }; + subdomain = lib.mkOption { + default = "grafana"; + type = lib.types.str; + description = '' + The subdomain where Grafana is reachable + ''; + }; + host = lib.mkOption { default = "localhost"; type = lib.types.str; @@ -41,13 +57,6 @@ in config = lib.mkMerge [ (lib.mkIf cfg.enable { - age.secrets = { - grafana-admin-pwd = { - file = ../../secrets/grafana-admin-pwd.age; - owner = "grafana"; - }; - }; - services = { grafana = { @@ -63,13 +72,14 @@ in }; security = { admin_user = "pazpi"; - admin_password = "$__file{${config.age.secrets.grafana-admin-pwd.path}}"; + admin_password = "$__file{${cfg.adminPasswordFile}}"; }; server = { - domain = "grafana.neon-dory.ts.net"; + # domain = "grafana.neon-dory.ts.net"; + domain = cfg.proxy.domain; http_addr = "0.0.0.0"; http_port = 3000; - # root_url = "https://grafana.${cfg.proxy.domain}"; + root_url = "https://${cfg.proxy.subdomain}.${cfg.proxy.domain}"; enable_gzip = true; }; users = { @@ -111,9 +121,9 @@ in (lib.mkIf cfg.proxy.enable { services.caddy = with cfg.proxy; { - virtualHosts."grafana.${domain}".extraConfig = '' + virtualHosts."${subdomain}.${domain}".extraConfig = '' reverse_proxy http://${host}:3000 - import cloudflare + import cloudflare_${domain} ''; }; }) diff --git a/modules/monitoring/prometheus.nix b/modules/monitoring/prometheus.nix index d870ba1..29c190b 100644 --- a/modules/monitoring/prometheus.nix +++ b/modules/monitoring/prometheus.nix @@ -26,6 +26,14 @@ in ''; }; + subdomain = lib.mkOption { + default = "prometheus"; + type = lib.types.str; + description = '' + The subdomain where Prometheus is reachable + ''; + }; + host = lib.mkOption { default = "localhost"; type = lib.types.str; @@ -94,9 +102,9 @@ in (lib.mkIf cfg.proxy.enable { services.caddy = with cfg.proxy; { - virtualHosts."prometheus.${domain}".extraConfig = '' + virtualHosts."${subdomain}.${domain}".extraConfig = '' reverse_proxy http://${host}:9090 - import cloudflare + import cloudflare_${domain} ''; }; }) diff --git a/modules/monitoring/uptime-kuma.nix b/modules/monitoring/uptime-kuma.nix index bbb3bdf..b615b6a 100644 --- a/modules/monitoring/uptime-kuma.nix +++ b/modules/monitoring/uptime-kuma.nix @@ -52,7 +52,7 @@ in services.caddy = with cfg.proxy; { virtualHosts."up.${domain}".extraConfig = '' reverse_proxy http://${host}:${port} - import cloudflare + import cloudflare_${domain} ''; }; }) diff --git a/modules/networking/caddy.nix b/modules/networking/caddy.nix index e426d8e..87f13bb 100644 --- a/modules/networking/caddy.nix +++ b/modules/networking/caddy.nix @@ -14,53 +14,81 @@ in options.my.networking.caddy = { enable = lib.mkEnableOption "Enable caddy as reverse proxy"; - domain = lib.mkOption { - default = "example.com"; - type = lib.types.str; + domainsList = lib.mkOption { + type = lib.types.listOf (lib.types.attrsOf lib.types.str); description = '' - The domain where Caddy is reachable + A list of sets, each containing three parameters of type string: domain, email, and cloudflareApiKeyFile. ''; + default = [ + { + domain = "example.com"; + email = "user@domain.com"; + cloudflareApiKeyFile = "/path/to/cloudflare/api/key"; + } + ]; }; - email = lib.mkOption { - default = "user@domain.com"; - type = lib.types.str; - description = '' - Email for Certbot - ''; - }; + # claudflareApiKeyFile = lib.mkOption { + # default = ""; + # type = lib.types.str; + # description = '' + # Cloudflare API key file + # ''; + # }; + + # domain = lib.mkOption { + # default = "example.com"; + # type = lib.types.str; + # description = '' + # The domain where Caddy is reachable + # ''; + # }; + + # email = lib.mkOption { + # default = "user@domain.com"; + # type = lib.types.str; + # description = '' + # Email for Certbot + # ''; + # }; }; config = lib.mkIf cfg.enable { - age.secrets = { - cloudflare-tegola-apiKey = { - file = ../../secrets/cloudflare-tegola-apiKey.age; - owner = config.services.caddy.user; - group = config.services.caddy.group; - }; - }; - # Insted on relying on caddy to provide TLS, we use certbot to get a certificate # https://aottr.dev/posts/2024/08/homelab-setting-up-caddy-reverse-proxy-with-ssl-on-nixos/ security.acme = { acceptTerms = true; - defaults.email = cfg.email; # TESTING ONLY! # defaults.server = "https://acme-staging-v02.api.letsencrypt.org/directory"; - certs."${cfg.domain}" = { - group = config.services.caddy.group; + certs = lib.mkMerge ( + map (domainConfig: { + "${domainConfig.domain}" = { + group = config.services.caddy.group; + email = domainConfig.email; + domain = domainConfig.domain; + extraDomainNames = [ "*.${domainConfig.domain}" ]; + dnsProvider = "cloudflare"; + dnsResolver = "1.1.1.1:53"; + dnsPropagationCheck = true; + environmentFile = domainConfig.cloudflareApiKeyFile; + }; + }) cfg.domainsList + ); - domain = "${cfg.domain}"; - extraDomainNames = [ "*.${cfg.domain}" ]; - dnsProvider = "cloudflare"; - dnsResolver = "1.1.1.1:53"; - dnsPropagationCheck = true; - environmentFile = config.age.secrets.cloudflare-tegola-apiKey.path; - }; + # certs."${cfg.domain}" = { + # group = config.services.caddy.group; + + # domain = "${cfg.domain}"; + # extraDomainNames = [ "*.${cfg.domain}" ]; + # dnsProvider = "cloudflare"; + # dnsResolver = "1.1.1.1:53"; + # dnsPropagationCheck = true; + # environmentFile = cfg.claudflareApiKeyFile; + # }; }; services.caddy = { @@ -72,17 +100,33 @@ in } ''; - extraConfig = - let - certPath = config.security.acme.certs."${cfg.domain}".directory; - in - '' - (cloudflare) { - tls ${certPath}/cert.pem ${certPath}/key.pem { - protocols tls1.3 + extraConfig = lib.concatStringsSep "\n" ( + map ( + domainConfig: + let + certPath = config.security.acme.certs."${domainConfig.domain}".directory; + in + '' + (cloudflare_${domainConfig.domain}) { + tls ${certPath}/cert.pem ${certPath}/key.pem { + protocols tls1.3 + } } - } - ''; + '' + ) cfg.domainsList + ); + + # extraConfig = + # let + # certPath = config.security.acme.certs."${cfg.domain}".directory; + # in + # '' + # (cloudflare) { + # tls ${certPath}/cert.pem ${certPath}/key.pem { + # protocols tls1.3 + # } + # } + # ''; }; systemd.services.caddy.serviceConfig = { diff --git a/modules/services/media-mgr.nix b/modules/services/media-mgr.nix index 6c42691..5147daa 100644 --- a/modules/services/media-mgr.nix +++ b/modules/services/media-mgr.nix @@ -135,31 +135,31 @@ in services.caddy = with cfg.proxy; { virtualHosts."prowlarr.${domain}".extraConfig = '' reverse_proxy http://${host}:9696 - import cloudflare + import cloudflare_${domain} ''; virtualHosts."radarr.${domain}".extraConfig = '' reverse_proxy http://${host}:7878 - import cloudflare + import cloudflare_${domain} ''; virtualHosts."sonarr.${domain}".extraConfig = '' reverse_proxy http://${host}:8989 - import cloudflare + import cloudflare_${domain} ''; virtualHosts."lidarr.${domain}".extraConfig = '' reverse_proxy http://${host}:8686 - import cloudflare + import cloudflare_${domain} ''; virtualHosts."readarr.${domain}".extraConfig = '' reverse_proxy http://${host}:8787 - import cloudflare + import cloudflare_${domain} ''; virtualHosts."bazarr.${domain}".extraConfig = '' reverse_proxy http://${host}:6767 - import cloudflare + import cloudflare_${domain} ''; virtualHosts."jellyseerr.${domain}".extraConfig = '' reverse_proxy http://${host}:5055 - import cloudflare + import cloudflare_${domain} ''; }; }) diff --git a/modules/services/nextcloud.nix b/modules/services/nextcloud.nix index a045cb4..36ad653 100644 --- a/modules/services/nextcloud.nix +++ b/modules/services/nextcloud.nix @@ -12,6 +12,14 @@ in options.my.services.nextcloud = { enable = lib.mkEnableOption "Enable Nextcloud module"; + adminPasswordFile = lib.mkOption { + default = ""; + type = lib.types.str; + description = '' + Path to the file containing the admin password for Nextcloud + ''; + }; + proxy = { enable = lib.mkEnableOption "Set the proxy entry for this service"; @@ -23,6 +31,22 @@ in ''; }; + subdomain = lib.mkOption { + default = "nextcloud"; + type = lib.types.str; + description = '' + The subdomain where Nextcloud is reachable + ''; + }; + + officeSubdomain = lib.mkOption { + default = "office"; + type = lib.types.str; + description = '' + The subdomain where Collabora Online is reachable + ''; + }; + host = lib.mkOption { default = "localhost"; type = lib.types.str; @@ -37,15 +61,6 @@ in config = lib.mkMerge [ (lib.mkIf cfg.enable { - age.secrets = { - nextcloud-admin-pwd = { - file = ../../secrets/nextcloud-admin-pwd.age; - owner = "nextcloud"; - group = "nextcloud"; - mode = "770"; - }; - }; - services = { nextcloud = { @@ -90,7 +105,7 @@ in config = { dbtype = "pgsql"; adminuser = "admin"; - adminpassFile = config.age.secrets.nextcloud-admin-pwd.path; + adminpassFile = cfg.adminPasswordFile; }; # Let NixOS install and configure the database automatically. @@ -160,12 +175,12 @@ in (lib.mkIf cfg.proxy.enable { services.caddy = with cfg.proxy; { - virtualHosts."cloud.${domain}".extraConfig = '' + virtualHosts."${subdomain}.${domain}".extraConfig = '' reverse_proxy http://${host}:80 - import cloudflare + import cloudflare_${domain} ''; - virtualHosts."office.${domain}".extraConfig = '' - import cloudflare + virtualHosts."${officeSubdomain}.${domain}".extraConfig = '' + import cloudflare_${domain} reverse_proxy http://${host}:${toString config.services.collabora-online.port} { # Required to circumvent bug of Onlyoffice loading mixed non-https content header_up X-Forwarded-Proto https diff --git a/modules/services/searx.nix b/modules/services/searx.nix index 44c0f27..4e02f0b 100644 --- a/modules/services/searx.nix +++ b/modules/services/searx.nix @@ -12,6 +12,14 @@ in options.my.services.searx = { enable = lib.mkEnableOption "Enable searXNG module"; + secretFile = lib.mkOption { + default = ""; + type = lib.types.str; + description = '' + Path to the file containing the secret for searXNG + ''; + }; + proxy = { enable = lib.mkEnableOption "Set the proxy entry for this service"; @@ -37,12 +45,10 @@ in config = lib.mkMerge [ (lib.mkIf cfg.enable { - age.secrets.searx-secret.file = ../../secrets/searx-secret.age; - - services.searcx = { + services.searx = { enable = true; redisCreateLocally = true; - environmentFile = config.age.secrets.searx-secret.path; + environmentFile = cfg.secretFile; settings = { general = { open_metrics = "@METRICS_SECRET@"; @@ -63,7 +69,7 @@ in services.caddy = with cfg.proxy; { virtualHosts."search.${domain}".extraConfig = '' reverse_proxy http://${host}:8080 - import cloudflare + import cloudflare_${domain} ''; }; }) diff --git a/modules/services/vaultwarden.nix b/modules/services/vaultwarden.nix index 1741285..003845e 100644 --- a/modules/services/vaultwarden.nix +++ b/modules/services/vaultwarden.nix @@ -13,6 +13,14 @@ in options.my.services.vaultwarden = { enable = lib.mkEnableOption "Enable Vaultwarden module"; + adminPasswordFile = lib.mkOption { + default = ""; + type = lib.types.str; + description = '' + Path to the file containing the admin password for Vaultwarden + ''; + }; + proxy = { enable = lib.mkEnableOption "Set the proxy entry for this service"; @@ -24,6 +32,14 @@ in ''; }; + subdomain = lib.mkOption { + default = "vault"; + type = lib.types.str; + description = '' + The subdomain where Vaultwarden is reachable + ''; + }; + host = lib.mkOption { default = "localhost"; type = lib.types.str; @@ -38,8 +54,6 @@ in config = lib.mkMerge [ (lib.mkIf cfg.enable { - age.secrets.vaultwarden-admin-pwd.file = ../../secrets/vaultwarden-admin-pwd.age; - my.services.postgresql = { enable = true; ensures = [ @@ -53,7 +67,7 @@ in services.vaultwarden = { enable = true; dbBackend = "postgresql"; - environmentFile = config.age.secrets.vaultwarden-admin-pwd.path; + environmentFile = cfg.adminPasswordFile; config = { DOMAIN = "https://vault.${cfg.proxy.domain}"; SENDS_ALLOWED = true; @@ -75,9 +89,9 @@ in (lib.mkIf cfg.proxy.enable { services.caddy = with cfg.proxy; { - virtualHosts."vault.${domain}".extraConfig = '' + virtualHosts."${subdomain}.${domain}".extraConfig = '' reverse_proxy http://${host}:${toString rocketPort} - import cloudflare + import cloudflare_${domain} ''; }; }) diff --git a/modules/virtualisation/portainer.nix b/modules/virtualisation/portainer.nix index ee6eb2a..7398e81 100644 --- a/modules/virtualisation/portainer.nix +++ b/modules/virtualisation/portainer.nix @@ -43,61 +43,103 @@ in ''; }; - }; - - config = lib.mkIf cfg.enable { - - my.virtualisation.docker.enable = true; - - virtualisation.oci-containers = { - backend = "docker"; # Use Docker as the backend - - containers = { - portainer = { - image = "portainer/portainer-ce:latest"; - ports = [ "9000:9000" ]; - volumes = [ - "/var/run/docker.sock:/var/run/docker.sock" - "${cfg.portainerDataDir}:/data" # Add persistent volume for Portainer data - ]; - environmentFiles = [ cfg.environmentSecrets ]; - labels = { - "com.centurylinklabs.watchtower.enable" = "true"; - }; - autoStart = true; - }; - - watchtower = lib.mkIf cfg.enableWatchtower { - image = "containrrr/watchtower"; - volumes = [ "/var/run/docker.sock:/var/run/docker.sock" ]; - autoStart = true; - environmentFiles = [ cfg.environmentSecrets ]; - environment = { - "TZ" = "Europe/Rome"; - "WATCHTOWER_CLEANUP" = "true"; - "WATCHTOWER_SCHEDULE" = "0 0 4 * * *"; # Run every day at 4am - "WATCHTOWER_LABEL_ENABLE" = "true"; # Only update labeled containers - "WATCHTOWER_NOTIFICATIONS" = "shoutrrr"; # Use shoutrrr for notifications - }; - }; + proxy = { + enable = lib.mkEnableOption "Set the proxy entry for this service"; + domain = lib.mkOption { + default = "example.com"; + type = lib.types.str; + description = '' + The domain where Caddy is reachable + ''; }; + + subdomain = lib.mkOption { + default = "portainer"; + type = lib.types.str; + description = '' + The subdomain where Portainer will be reachable + ''; + }; + + host = lib.mkOption { + default = "localhost"; + type = lib.types.str; + description = '' + host name where the download manager stack is running + ''; + }; + }; - # Ensure the directory exists and has the correct permissions - systemd.tmpfiles.settings = { - "10-portainerDataDir" = { - ${cfg.portainerDataDir} = { - d = { - group = "root"; - mode = "0755"; - user = "root"; + }; + + config = lib.mkMerge [ + (lib.mkIf cfg.enable { + + my.virtualisation.docker.enable = true; + + virtualisation.oci-containers = { + backend = "docker"; # Use Docker as the backend + + containers = { + portainer = { + image = "portainer/portainer-ce:latest"; + ports = [ "9000:9000" ]; + volumes = [ + "/var/run/docker.sock:/var/run/docker.sock" + "${cfg.portainerDataDir}:/data" # Add persistent volume for Portainer data + ]; + environmentFiles = [ cfg.environmentSecrets ]; + labels = { + "com.centurylinklabs.watchtower.enable" = "true"; + }; + autoStart = true; + }; + + watchtower = lib.mkIf cfg.enableWatchtower { + image = "containrrr/watchtower"; + volumes = [ "/var/run/docker.sock:/var/run/docker.sock" ]; + autoStart = true; + environmentFiles = [ cfg.environmentSecrets ]; + environment = { + "TZ" = "Europe/Rome"; + "WATCHTOWER_CLEANUP" = "true"; + "WATCHTOWER_SCHEDULE" = "0 0 4 * * *"; # Run every day at 4am + "WATCHTOWER_LABEL_ENABLE" = "true"; # Only update labeled containers + "WATCHTOWER_NOTIFICATIONS" = "shoutrrr"; # Use shoutrrr for notifications + }; + }; + + }; + }; + + # Ensure the directory exists and has the correct permissions + systemd.tmpfiles.settings = { + "10-portainerDataDir" = { + ${cfg.portainerDataDir} = { + d = { + group = "root"; + mode = "0755"; + user = "root"; + }; }; }; }; - }; - networking.firewall.allowedTCPPorts = [ 9000 ]; + networking.firewall.allowedTCPPorts = [ 9000 ]; + + }) + + (lib.mkIf cfg.proxy.enable { + services.caddy = with cfg.proxy; { + virtualHosts."${subdomain}.${domain}".extraConfig = '' + reverse_proxy http://${host}:9000 + import cloudflare_${domain} + ''; + }; + }) + + ]; - }; }