191 lines
6.8 KiB
Nix
191 lines
6.8 KiB
Nix
{ config, lib, pkgs, homeyConfig, ... }:
|
|
|
|
# Caddy reverse proxy.
|
|
#
|
|
# Features:
|
|
# - DNS-01 ACME via Cloudflare API → real wildcard cert for *.home.zakobar.com
|
|
# - forward_auth to Authelia for protected vhosts
|
|
# - Plain reverse_proxy for public vhosts (authelia itself, nextcloud)
|
|
# - Listens on :80 (redirect) and :443 (TLS)
|
|
#
|
|
# Because nixpkgs ships Caddy without the cloudflare DNS plugin by default,
|
|
# we build a custom Caddy with it using the xcaddy wrapper from nixpkgs.
|
|
#
|
|
# Secrets consumed from sops:
|
|
# cloudflare/api_token
|
|
|
|
let
|
|
cfg = config.homey.caddy;
|
|
domain = homeyConfig.domain;
|
|
|
|
# Build Caddy with the Cloudflare DNS plugin using the nixos-25.05 API.
|
|
# `withPlugins` is a passthru function on the caddy package; it uses xcaddy
|
|
# under the hood to produce a fixed-output derivation.
|
|
caddyWithCloudflare = pkgs.caddy.withPlugins {
|
|
plugins = [
|
|
"github.com/caddy-dns/cloudflare@v0.2.2-0.20250724223520-f589a18c0f5d"
|
|
];
|
|
hash = "sha256-2Fb2fgM7YhWk9kBnnNGb85MJkAkgzXiI1fb6eK3ykIE=";
|
|
};
|
|
|
|
# Reusable Authelia forward_auth snippet
|
|
# Returns a Caddyfile snippet block that applies forward_auth.
|
|
# copy_headers makes Authelia's Remote-* headers available downstream.
|
|
autheliaForwardAuth = ''
|
|
forward_auth localhost:9091 {
|
|
uri /api/verify?rd=https://auth.${domain}
|
|
copy_headers Remote-User Remote-Name Remote-Groups Remote-Email
|
|
# On auth failure, redirect to the authelia login page
|
|
@goauth status 401
|
|
handle_response @goauth {
|
|
redir https://auth.${domain}?rm={method} 302
|
|
}
|
|
}
|
|
'';
|
|
|
|
in
|
|
{
|
|
options.homey.caddy = {
|
|
enable = lib.mkEnableOption "Caddy reverse proxy";
|
|
|
|
acmeEmail = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "admin@zakobar.com";
|
|
description = "Email for Let's Encrypt ACME registration.";
|
|
};
|
|
};
|
|
|
|
config = lib.mkIf cfg.enable {
|
|
# -----------------------------------------------------------------------
|
|
# Secrets
|
|
# -----------------------------------------------------------------------
|
|
sops.secrets."cloudflare/api_token" = {
|
|
owner = config.services.caddy.user;
|
|
};
|
|
|
|
# -----------------------------------------------------------------------
|
|
# Caddy service
|
|
# -----------------------------------------------------------------------
|
|
services.caddy = {
|
|
enable = true;
|
|
package = caddyWithCloudflare;
|
|
|
|
# Global options
|
|
globalConfig = ''
|
|
email ${cfg.acmeEmail}
|
|
# Use Cloudflare DNS-01 challenge for wildcard cert
|
|
acme_dns cloudflare {env.CLOUDFLARE_API_TOKEN}
|
|
'';
|
|
|
|
# Each virtual host
|
|
virtualHosts = {
|
|
|
|
# ------------------------------------------------------------------
|
|
# Authelia — public, no auth gate (it IS the auth gate)
|
|
# ------------------------------------------------------------------
|
|
"auth.${domain}" = {
|
|
extraConfig = ''
|
|
reverse_proxy localhost:9091
|
|
'';
|
|
};
|
|
|
|
# ------------------------------------------------------------------
|
|
# Gitea — protected behind one_factor Authelia
|
|
# ------------------------------------------------------------------
|
|
"git.${domain}" = {
|
|
extraConfig = ''
|
|
${autheliaForwardAuth}
|
|
reverse_proxy localhost:3000
|
|
'';
|
|
};
|
|
|
|
# ------------------------------------------------------------------
|
|
# Nextcloud — public auth (Nextcloud manages its own users + LDAP)
|
|
# Authelia is not gating nextcloud directly because NC has its own
|
|
# login flow. We still want HTTPS.
|
|
# ------------------------------------------------------------------
|
|
"nextcloud.${domain}" = {
|
|
extraConfig = ''
|
|
# Redirect CardDAV/CalDAV discovery
|
|
redir /.well-known/carddav /remote.php/dav/ 301
|
|
redir /.well-known/caldav /remote.php/dav/ 301
|
|
|
|
# Large uploads (5 GB)
|
|
request_body {
|
|
max_size 5GB
|
|
}
|
|
|
|
reverse_proxy localhost:8080
|
|
'';
|
|
};
|
|
|
|
# ------------------------------------------------------------------
|
|
# phpLDAPadmin — two_factor, admins only (enforced by authelia policy)
|
|
# ------------------------------------------------------------------
|
|
"ldapadmin.${domain}" = {
|
|
extraConfig = ''
|
|
${autheliaForwardAuth}
|
|
reverse_proxy localhost:8081
|
|
'';
|
|
};
|
|
|
|
# ------------------------------------------------------------------
|
|
# Jellyfin — one_factor (added when enabled)
|
|
# ------------------------------------------------------------------
|
|
"jellyfin.${domain}" = {
|
|
extraConfig = ''
|
|
${autheliaForwardAuth}
|
|
reverse_proxy localhost:8096
|
|
'';
|
|
};
|
|
|
|
# ------------------------------------------------------------------
|
|
# Transmission — two_factor, admins only (enforced by authelia policy)
|
|
# ------------------------------------------------------------------
|
|
"torrent.${domain}" = {
|
|
extraConfig = ''
|
|
${autheliaForwardAuth}
|
|
reverse_proxy localhost:9092
|
|
'';
|
|
# NOTE: transmission is bound to 9092 to avoid clash with authelia on 9091.
|
|
};
|
|
|
|
};
|
|
};
|
|
|
|
# -----------------------------------------------------------------------
|
|
# Pass Cloudflare token as env var to the caddy systemd unit.
|
|
#
|
|
# The caddy-dns/cloudflare plugin reads CLOUDFLARE_API_TOKEN directly.
|
|
# sops decrypts the secret to a file at runtime; we write a transient
|
|
# env file to /run/ in ExecStartPre so systemd picks it up via
|
|
# EnvironmentFile. The file is removed in ExecStopPost.
|
|
# -----------------------------------------------------------------------
|
|
systemd.services.caddy = {
|
|
serviceConfig = {
|
|
EnvironmentFile = "/run/caddy-secrets.env";
|
|
ExecStartPre = [
|
|
(pkgs.writeShellScript "caddy-inject-cf-token" ''
|
|
install -m 0600 /dev/null /run/caddy-secrets.env
|
|
printf 'CLOUDFLARE_API_TOKEN=%s\n' \
|
|
"$(cat ${config.sops.secrets."cloudflare/api_token".path})" \
|
|
> /run/caddy-secrets.env
|
|
'')
|
|
];
|
|
ExecStopPost = [
|
|
(pkgs.writeShellScript "caddy-cleanup-env" ''
|
|
rm -f /run/caddy-secrets.env
|
|
'')
|
|
];
|
|
};
|
|
after = lib.mkAfter [ "podman-authelia.service" ];
|
|
wants = lib.mkAfter [ "podman-authelia.service" ];
|
|
};
|
|
|
|
# -----------------------------------------------------------------------
|
|
# Firewall — open HTTP + HTTPS (already in common.nix, explicit here too)
|
|
# -----------------------------------------------------------------------
|
|
networking.firewall.allowedTCPPorts = [ 80 443 ];
|
|
};
|
|
}
|