263 lines
7.1 KiB
Nix
263 lines
7.1 KiB
Nix
{
|
|
config,
|
|
pkgs,
|
|
lib,
|
|
...
|
|
}:
|
|
|
|
with lib;
|
|
|
|
let
|
|
cfg = config.my.networking.caddy;
|
|
in
|
|
{
|
|
options.my.networking.caddy = {
|
|
enable = lib.mkEnableOption "Enable caddy as reverse proxy";
|
|
|
|
domainsList = lib.mkOption {
|
|
type = lib.types.listOf (lib.types.attrsOf lib.types.str);
|
|
description = ''
|
|
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";
|
|
}
|
|
];
|
|
};
|
|
|
|
dynamicdnsDomains = lib.mkOption {
|
|
type = lib.types.listOf (lib.types.attrsOf lib.types.str);
|
|
description = ''
|
|
A list of domains to update with the dynamicdns plugin.
|
|
'';
|
|
default = [
|
|
{
|
|
domain = "example.com";
|
|
cloudflareApiEnvName = "CLOUDFLARE_API_TOKEN_MY_DOMAIN";
|
|
}
|
|
];
|
|
};
|
|
|
|
configEnvFile = lib.mkOption {
|
|
type = lib.types.path;
|
|
description = ''
|
|
Path to the environment file that contains the secrets like Cloudflare API key.
|
|
In order to use the dynamicdns plugin, you need to set "cloudflareApiEnvName" for each domain in the dynamicdnsDomains list.
|
|
'';
|
|
default = "";
|
|
};
|
|
|
|
# List of extra caddy.virtualHost
|
|
extraVirtualHosts = lib.mkOption {
|
|
type = lib.types.listOf (
|
|
lib.types.submodule {
|
|
options = {
|
|
subdomain = lib.mkOption {
|
|
type = lib.types.str;
|
|
description = "The subdomain for the virtual host.";
|
|
};
|
|
host = lib.mkOption {
|
|
type = lib.types.str;
|
|
description = "The host address for the reverse proxy.";
|
|
};
|
|
domain = lib.mkOption {
|
|
type = lib.types.str;
|
|
description = "The domain for the virtual host.";
|
|
};
|
|
};
|
|
}
|
|
);
|
|
description = ''
|
|
A list of virtual hosts outside nixos/colmena config.
|
|
'';
|
|
default = [ ];
|
|
};
|
|
|
|
};
|
|
|
|
config = lib.mkIf cfg.enable {
|
|
|
|
# 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;
|
|
|
|
# TESTING ONLY!
|
|
# defaults.server = "https://acme-staging-v02.api.letsencrypt.org/directory";
|
|
|
|
certs = lib.mkMerge (
|
|
map (domainConfig: {
|
|
"${domainConfig.domain}" = {
|
|
group = config.services.caddy.group;
|
|
email = domainConfig.email;
|
|
domain = domainConfig.domain;
|
|
extraDomainNames = [
|
|
"*.${domainConfig.domain}"
|
|
"*.ts.${domainConfig.domain}"
|
|
];
|
|
dnsProvider = "cloudflare";
|
|
dnsResolver = "1.1.1.1:53";
|
|
dnsPropagationCheck = true;
|
|
environmentFile = domainConfig.cloudflareApiKeyFile;
|
|
};
|
|
}) cfg.domainsList
|
|
);
|
|
|
|
};
|
|
|
|
services.caddy = {
|
|
enable = true;
|
|
|
|
package = pkgs.caddy.withPlugins {
|
|
hash = "sha256-PJYUk51p/h6mEsiPUczTNCSlq57y+9ALfOkTHev0ITI=";
|
|
plugins = [
|
|
"github.com/caddy-dns/cloudflare@v0.2.2-0.20250724223520-f589a18c0f5d"
|
|
"github.com/mholt/caddy-dynamicdns@v0.0.0-20250430031602-b846b9e8fb83"
|
|
];
|
|
};
|
|
|
|
globalConfig = ''
|
|
admin :2024
|
|
servers {
|
|
metrics
|
|
}
|
|
''
|
|
+ lib.concatStringsSep "\n" (
|
|
map (dynamicdnsDomain: ''
|
|
dynamic_dns {
|
|
provider cloudflare {env.${dynamicdnsDomain.cloudflareApiEnvName}}
|
|
domains {
|
|
${dynamicdnsDomain.domain} @ *
|
|
}
|
|
}
|
|
'') cfg.dynamicdnsDomains
|
|
);
|
|
|
|
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
|
|
)
|
|
+ "\n"
|
|
+ ''
|
|
(cors) {
|
|
@cors_preflight{args[0]} method OPTIONS
|
|
@cors{args[0]} header Origin {args[0]}
|
|
|
|
handle @cors_preflight{args[0]} {
|
|
header {
|
|
Access-Control-Allow-Origin *
|
|
Access-Control-Allow-Methods "GET, POST, PUT, PATCH, DELETE, OPTIONS"
|
|
Access-Control-Allow-Headers *
|
|
Access-Control-Max-Age "3600"
|
|
defer
|
|
}
|
|
respond "" 204
|
|
}
|
|
|
|
handle @cors{args[0]} {
|
|
header {
|
|
Access-Control-Allow-Origin "{args[0]}"
|
|
Access-Control-Expose-Headers *
|
|
defer
|
|
}
|
|
}
|
|
}
|
|
'';
|
|
|
|
virtualHosts = lib.foldl' (
|
|
acc: extraVirtualHost:
|
|
acc
|
|
// {
|
|
"${extraVirtualHost.subdomain}.${extraVirtualHost.domain}".extraConfig = ''
|
|
reverse_proxy ${extraVirtualHost.host}
|
|
import cloudflare_${extraVirtualHost.domain}
|
|
import cors https://${extraVirtualHost.subdomain}.${extraVirtualHost.domain}
|
|
'';
|
|
}
|
|
) { } cfg.extraVirtualHosts;
|
|
|
|
};
|
|
|
|
systemd.services.caddy.serviceConfig = {
|
|
AmbientCapabilities = "CAP_NET_BIND_SERVICE";
|
|
EnvironmentFile = cfg.configEnvFile;
|
|
};
|
|
|
|
# By default, the module create a custom user but it lacks permission to read caddy files
|
|
systemd.services.promtail.serviceConfig = {
|
|
Group = lib.mkForce config.services.caddy.group;
|
|
User = lib.mkForce config.services.caddy.user;
|
|
};
|
|
|
|
services.promtail = {
|
|
enable = true;
|
|
configuration = {
|
|
server.http_listen_port = 9080;
|
|
server.grpc_listen_port = 0;
|
|
clients = [ { url = "http://metrics.internal:3100/loki/api/v1/push"; } ];
|
|
|
|
scrape_configs = [
|
|
{
|
|
job_name = "journal";
|
|
journal = {
|
|
max_age = "12h";
|
|
labels = {
|
|
job = "systemd-journal";
|
|
};
|
|
};
|
|
relabel_configs = [
|
|
{
|
|
source_labels = [ "__journal__systemd_unit" ];
|
|
regex = "(.*)\\.service";
|
|
target_label = "service";
|
|
}
|
|
{
|
|
source_labels = [ "__journal__hostname" ];
|
|
target_label = "hostname";
|
|
}
|
|
];
|
|
}
|
|
{
|
|
job_name = "caddy";
|
|
static_configs = [
|
|
{
|
|
targets = [ "localhost" ];
|
|
labels = {
|
|
job = "caddylogs";
|
|
__path__ = "${config.services.caddy.logDir}/*.log";
|
|
};
|
|
}
|
|
];
|
|
}
|
|
];
|
|
};
|
|
};
|
|
|
|
networking.firewall.allowedTCPPorts = [
|
|
80
|
|
443
|
|
2024
|
|
];
|
|
|
|
networking.firewall.allowedUDPPorts = [
|
|
80
|
|
443
|
|
];
|
|
};
|
|
|
|
}
|