infra/nix/hosts/caelum/modules/caddy.nix
2026-01-05 23:09:19 +01:00

199 lines
5.3 KiB
Nix

{
lib,
config,
pkgs,
sops-nix,
...
}:
with lib;
let
p = config.sops.placeholder;
domain = p.domainName;
svc = "caddy.service";
caddyLogPath = "/var/log/caddy";
caddyLog = sub: if (sub != "") then ''
log {
hostnames ${sub}.${domain}
level INFO
format json
output file ${caddyLogPath}/${sub}.${domain}.log {
roll_size 100MiB
roll_keep 5
roll_keep_for 240d
}
}''
else
''
log {
hostnames ${domain}
level INFO
format json
output file ${caddyLogPath}/${domain}.log {
roll_size 100MiB
roll_keep 5
roll_keep_for 240d
}
}'';
in {
networking.firewall.allowedTCPPorts = [80 443];
services = {
caddy = {
enable = true;
configFile = config.sops.templates.caddyPls.path;
adapter = "caddyfile";
};
};
systemd = {
services = {
caddy = {
description = "Caddy web server";
after = ["network-online.target" "sops-nix.service"];
wants = ["network-online.target"];
wantedBy = ["multi-user.target"];
serviceConfig = {
TimeoutStopSec = "7s";
# LimitNOFILE = 1048576;
# LimitNPROC = 512;
PrivateTmp = true;
# ProtectSystem = "full";
AmbientCapabilities = "cap_net_bind_service";
};
};
caddy-watcher = {
description = "Caddy watcher";
wantedBy = ["multi-user.target"];
serviceConfig = {
Type = "oneshot";
ExecStart = "systemctl restart ${svc}";
};
};
};
# restart Caddy on config file change.
paths.caddy-watcher = {
pathConfig = {
PathChanged = config.sops.templates.caddyPls.path;
# Unit = svc;
};
wantedBy = ["paths.target"];
};
};
sops.secrets = {
"caddy/email".restartUnits = [svc];
"desecToken".restartUnits = [svc];
"domainName".restartUnits = [svc];
};
sops.templates.caddyPls = {
owner = config.systemd.services.caddy.serviceConfig.User;
content = ''
(hsts) {
header Strict-Transport-Security "max-age=86400; preload"
# header Strict-Transport-Security "max-age=15552000; preload"
# header Strict-Transport-Security "max-age=3600"
# header Strict-Transport-Security "max-age=86400"
}
(headersCommon) {
header / {
x-frame-options "sameorigin"
x-content-type-options "nosniff"
x-xss-protection "1; mode=block"
content-security-policy "
upgrade-insecure-requests;
default-src 'self';
style-src 'self';
script-src 'self';
font-src 'self';
img-src data: 'self';
form-action 'self';
connect-src 'self';
frame-ancestors 'none';
"
cross-origin-opener-policy "same-origin"
permissions-policy "geolocation=(), midi=(), sync-xhr=(), microphone=(), camera=(), magnetometer=(), gyroscope=(), fullscreen=(self), payment=()"
referrer-policy "strict-origin-when-cross-origin"
-Server
-server
Permissions-Policy interest-cohort=()
# Strict-Transport-Security "max-age=86400"
}
}
(authentik) {
# Always forward outpost path to actual outpost
reverse_proxy /outpost.goauthentik.io/* https://auth.${p.domainName}
# Forward authentication to outpost
forward_auth https://auth.${p.domainName} {
uri /outpost.goauthentik.io/auth/caddy
# Capitalization of the headers is important, otherwise they will be empty
copy_headers X-Authentik-Username X-Authentik-Groups X-Authentik-Email X-Authentik-Name X-Authentik-Uid X-Authentik-Jwt X-Authentik-Meta-Jwks X-Authentik-Meta-Outpost X-Authentik-Meta-Provider X-Authentik-Meta-App X-Authentik-Meta-Version
}
}
(tlsCommon) {
tls {
dns desec {
token ${p.desecToken}
}
# propagation_timeout 30s
# propagation_timeout 1m
# propagation_timeout 2m
propagation_timeout -1
#propagation_delay 30s
propagation_delay 90s
curves x25519
key_type p384
protocols tls1.2 tls1.3
# resolvers 1.1.1.1
# resolvers 8.8.8.8 8.8.4.4
# resolvers 1.1.1.1 8.8.8.8
}
}
{
# acme_ca https://acme-staging-v02.api.letsencrypt.org/directory
#admin off
# servers {
metrics
# }
acme_dns desec {
token ${p.desecToken}
}
email ${p."caddy/email"}
# grace_period 60s
grace_period 30s
log default {
output stdout
format json
# level INFO
level DEBUG
}
# import tlsCommon
}
# *.${domain}, ${domain} {
*.${domain} {
import tlsCommon
${caddyLog "ollama"}
@ollama host ollama.${domain}
handle @ollama {
encode zstd br
import headersCommon
import hsts
reverse_proxy localhost:${toString config.services.ollama.port}
}
} # end *.${domain}, ${domain}
'';
};
}