Files
nixos-config/servers/hetzner/modules/services/caddy.nix
T
jory cba6b18914 Harden server and add Nix-native CI + self-hosted Gitea Actions
- caddy: security headers (X-Content-Type-Options/X-XSS-Protection/
  X-Frame-Options) on all vhosts + baseline CSP; strip SnappyMail
  upstream copies via header_down on mail.severijnse.eu
- tlsa-updater: compute TLSA 3 1 1 from cert SPKI (SHA-256), sync
  _25/_465/_993, fail-safe placeholders; coredns zone updated
- pre-commit: wire cachix/git-hooks.nix (alejandra, statix, actionlint,
  ...); CI pre-commit job over x86_64 + aarch64 matrix
- gitea: enable Gitea Actions + self-hosted runner (native:host,
  aarch64 via binfmt); add .gitea/workflows/ci.yml and local hook
- fix statix warnings (merge repeated systemd/database/configFile keys,
  inherit, bool-compare guards); add missing trailing newlines
2026-07-12 01:48:46 +02:00

243 lines
6.7 KiB
Nix

{...}: let
antiScrape = ''
@bad_bot {
header_regexp User-Agent "(?i)(scrapy|cpython-requests|python-requests|curl|wget|go-http-client|ltx71|petalbot|bytespider|dotbot|ahrefsbot|semrushbot|mj12bot|dataforseo|facebookexternalhit|claudebot|anthropic-ai|perplexity|gptbot|chatgpt-user|omnisci|imgproxy|ccbot|exabot|360spider|baiduspider|sogou|duckduckgo|amazonbot|cohere-ai|diffbot|imagesiftbot).*"
}
respond @bad_bot "" 444
header {
X-Robots-Tag "noindex, nofollow, noai, noimageai"
}
'';
in {
services.caddy = {
enable = true;
group = "caddy";
dataDir = "/var/lib/caddy";
logDir = "/var/log/caddy";
globalConfig = ''
email jory@severijnse.eu
servers {
trusted_proxies static private_ranges
}
'';
# Global Caddyfile snippets (shared across all virtual hosts).
extraConfig = ''
(admin_gate) {
@notvpn not remote_ip 10.8.0.0/24
respond @notvpn "Forbidden" 403
}
# Security headers applied to every response of every site that
# imports this snippet (covers all current and future hosts).
# For proxied hosts whose upstream sets its own copies, strip them
# with `header_down` inside the reverse_proxy block instead.
(security_headers) {
header {
X-Content-Type-Options "nosniff"
X-XSS-Protection "0"
X-Frame-Options "SAMEORIGIN"
}
}
(csp) {
header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; object-src 'none'; base-uri 'self'; frame-ancestors 'self'; form-action 'self'; connect-src 'self' wss: ws:"
}
'';
virtualHosts = {
"severijnse.eu" = {
extraConfig = ''
import security_headers
import csp
${antiScrape}
@logo path /logo.svg
handle @logo {
root * /srv
file_server
header Content-Type image/svg+xml
header Cache-Control "public, immutable, max-age=31536000"
header X-Content-Type-Options nosniff
}
handle {
redir https://jory.severijnse.eu{uri} permanent
}
'';
};
"www.severijnse.eu" = {
extraConfig = ''
import security_headers
import csp
${antiScrape}
redir https://jory.severijnse.eu{uri} permanent
'';
};
"jory.severijnse.eu" = {
extraConfig = ''
import security_headers
import csp
${antiScrape}
root * /srv/jory
file_server
try_files {path} /index.html
encode zstd gzip
header Strict-Transport-Security "max-age=31536000;"
@robots path /robots.txt
handle @robots {
header Content-Type text/plain
respond `User-agent: *
Allow: /
User-agent: Googlebot
Allow: /
User-agent: Bingbot
Allow: /
User-agent: Twitterbot
Allow: /
User-agent: facebookexternalhit
Allow: /
User-agent: GPTBot
User-agent: ChatGPT-User
User-agent: OAI-SearchBot
User-agent: ClaudeBot
User-agent: Claude-Web
User-agent: anthropic-ai
User-agent: PerplexityBot
User-agent: Bytespider
User-agent: Amazonbot
User-agent: CCBot
User-agent: Google-Extended
User-agent: Applebot-Extended
Disallow: /
` 200
}
'';
};
"mta-sts.severijnse.eu" = {
extraConfig = ''
import security_headers
import csp
root * /srv
file_server
header Content-Type text/plain
header Cache-Control "public, max-age=300"
header X-Content-Type-Options nosniff
'';
};
"vault.severijnse.eu" = {
extraConfig = ''
import security_headers
import csp
${antiScrape}
import admin_gate
header Strict-Transport-Security "max-age=31536000;"
reverse_proxy 127.0.0.1:1001
encode zstd gzip
'';
};
"git.severijnse.eu" = {
extraConfig = ''
import security_headers
import csp
${antiScrape}
reverse_proxy 127.0.0.1:3000
encode zstd gzip
'';
};
"mail.severijnse.eu" = {
extraConfig = ''
import security_headers
${antiScrape}
reverse_proxy 127.0.0.1:8888 {
# Strip copies set by the upstream SnappyMail container so we
# emit exactly one correct value of each security header.
header_down -X-Frame-Options
header_down -X-XSS-Protection
header_down -X-Content-Type-Options
}
encode zstd gzip
'';
};
"vpn.severijnse.eu" = {
extraConfig = ''
import security_headers
import csp
reverse_proxy 127.0.0.1:51821
encode zstd gzip
'';
};
"mine.severijnse.eu" = {
extraConfig = ''
import security_headers
import csp
${antiScrape}
reverse_proxy 127.0.0.1:81
'';
};
"music.severijnse.eu" = {
extraConfig = ''
import security_headers
import csp
${antiScrape}
reverse_proxy 127.0.0.1:4321
'';
};
"fail2ban.severijnse.eu" = {
extraConfig = ''
import security_headers
import csp
${antiScrape}
import admin_gate
reverse_proxy 127.0.0.1:8080
'';
};
"automate.severijnse.eu" = {
extraConfig = ''
import security_headers
import csp
${antiScrape}
import admin_gate
reverse_proxy 127.0.0.1:5678
encode zstd gzip
'';
};
"pay.severijnse.eu" = {
extraConfig = ''
import security_headers
import csp
${antiScrape}
import admin_gate
reverse_proxy 127.0.0.1:5000
encode zstd gzip
'';
};
"http://ip.severijnse.eu" = {
extraConfig = ''
import security_headers
import csp
header Content-Type text/plain
respond {client_ip} 200
'';
};
};
};
systemd.tmpfiles.rules = [
"d /srv 0755 caddy caddy -"
"d /srv/jory 0755 caddy caddy -"
];
}