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

197 lines
4.7 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";
};
};
# restart Caddy on config file change.
paths.caddy = {
pathConfig = {
PathChanged = config.sops.templates.caddyPls.path;
Unit = svc;
};
};
};
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"
}
}
(tlsCommon) {
tls {
dns desec {
token ${p.desecToken}
}
# propagation_timeout 30s
# propagation_timeout 1m
propagation_timeout 2m
# propagation_timeout -1
propagation_delay 30s
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 "cache"}
# attic - nix cache.
@cache host cache.${domain} nixcache.${domain}
handle @cache {
encode zstd br
#import tlsCommon
import headersCommon
import hsts
# import authentik
reverse_proxy localhost:${toString config.wanderllama.attic.port}
}
handle {
respond "unauthorised, sorry.
" 403
# abort
}
} # end *.${domain}, ${domain}
'';
};
}