caddy int

This commit is contained in:
mjallen18
2026-04-09 14:57:27 -05:00
parent b73ad049e7
commit 7cc6732a7e
4 changed files with 171 additions and 3 deletions

View File

@@ -0,0 +1,107 @@
{
config,
lib,
pkgs,
namespace,
...
}:
with lib;
let
name = "caddy-internal";
cfg = config.${namespace}.services.${name};
net = lib.${namespace}.network;
caddyPackage = pkgs.caddy.withPlugins {
plugins = [
"github.com/caddy-dns/cloudflare@v0.2.3"
];
hash = "sha256-20o+14cn/eeLuf1c8uGE1ODRZGC0oxocaIVlv4tFSvA=";
};
# Build a virtual-host block for one proxy entry.
# Access is restricted to LAN + Nebula subnets; all other clients get 403.
mkProxyBlock =
entryName: proxyCfg:
let
fqdn = "${proxyCfg.subdomain}.${net.domain}";
in
''
@${entryName} host ${fqdn}
handle @${entryName} {
@${entryName}_internal {
remote_ip ${net.subnet.lan} ${net.subnet.nebula}
host ${fqdn}
}
handle @${entryName}_internal {
reverse_proxy ${proxyCfg.upstream}
${proxyCfg.extraCaddyConfig}
}
handle {
respond "Forbidden" 403
}
}
'';
proxyBlocks = lib.concatStringsSep "\n" (
lib.mapAttrsToList mkProxyBlock (lib.filterAttrs (_: p: p.enable) cfg.proxies)
);
caddy-internal = lib.${namespace}.mkModule {
inherit config name;
description = "Internal-only Caddy reverse proxy with HTTPS via Cloudflare DNS challenge";
options = {
proxies = mkOption {
type = types.attrsOf (
types.submodule {
options = {
enable = lib.${namespace}.mkBoolOpt true "Whether to enable this proxy entry";
subdomain = lib.${namespace}.mkOpt types.str "" "Subdomain under ${net.domain}";
upstream = lib.${namespace}.mkOpt types.str "" "Upstream address (e.g. http://127.0.0.1:8123)";
extraCaddyConfig =
lib.${namespace}.mkOpt types.lines ""
"Extra Caddyfile directives for this entry";
};
}
);
default = { };
description = "Internal services to proxy, each restricted to LAN + Nebula subnets";
};
};
moduleConfig = {
services.caddy = {
enable = true;
package = caddyPackage;
environmentFile = config.sops.templates."caddy-internal.env".path;
email = "jalle008@proton.me";
enableReload = true;
dataDir = "${cfg.configDir}/caddy";
globalConfig = ''
metrics
http_port 80
https_port 443
default_bind 0.0.0.0
'';
virtualHosts."*.${net.domain}" = {
extraConfig = ''
tls {
dns cloudflare {$CLOUDFLARE_DNS_API_TOKEN}
}
${proxyBlocks}
'';
};
};
networking.firewall.allowedTCPPorts = [
80
443
];
};
};
in
{
imports = [
caddy-internal
./sops.nix
];
}

View File

@@ -0,0 +1,39 @@
{
config,
lib,
namespace,
...
}:
let
cfg = config.${namespace}.services.caddy-internal;
caddyUser = config.users.users.caddy.name;
caddyGroup = config.users.users.caddy.group;
caddySecret = {
owner = caddyUser;
group = caddyGroup;
sopsFile = lib.snowfall.fs.get-file "secrets/nuc-secrets.yaml";
restartUnits = [ "caddy.service" ];
};
in
{
config = lib.mkIf cfg.enable {
sops = {
secrets = {
# Add this key to secrets/nuc-secrets.yaml:
# nuc/caddy/cloudflare-dns-api-token: <token>
"nuc/caddy/cloudflare-dns-api-token" = caddySecret;
};
templates."caddy-internal.env" = {
content = ''
CLOUDFLARE_DNS_API_TOKEN=${config.sops.placeholder."nuc/caddy/cloudflare-dns-api-token"}
'';
owner = caddyUser;
group = caddyGroup;
restartUnits = [ "caddy.service" ];
};
};
};
}

View File

@@ -6,6 +6,8 @@ nuc:
nuc-nixos-cert: ENC[AES256_GCM,data:lRB9M1D7xMjf/XNxljM7wPitZzY8105Hu6GmmaBgenWIsewIoSfk3tTMwFEe8nzp2jraOzcurTEujl++YOy46S0FDg3bPzdlXBwWZw12F6akfgGTGl8XX7BB7vu9UiQ8stpgUd+6G8NhNmbmVw/0oDnEsSfd4KiJgyf8Hfd9wOBhw+xZEVzHJ+D98ixChH5RB1CyFDpK1qrU9+K7GhsRFEQQdIkr4Xv6ZKnQfcB/uuwQ9Po1vFyfZRmRbRrcYFWIhJSoBr7YY5Kn5LXJxpWPDLZGNe7zibE11J09gXBRXB2Oni2G7TEsNiMlY2w4SA4aBpVngyitw9So7hzwSPX+JCYZnHr3nBCDvkWaECvju9i6pclnuuEN4PFXghaxy3QOweWA4l1DF1jrgdQ+XU8VJk4H,iv:ZdGsr5AldRJYvoG+tXW2cnS7iEs4C14qjp7hQRNdKcI=,tag:fC/DbuFeSWA0vtn9fTV7bg==,type:str] nuc-nixos-cert: ENC[AES256_GCM,data:lRB9M1D7xMjf/XNxljM7wPitZzY8105Hu6GmmaBgenWIsewIoSfk3tTMwFEe8nzp2jraOzcurTEujl++YOy46S0FDg3bPzdlXBwWZw12F6akfgGTGl8XX7BB7vu9UiQ8stpgUd+6G8NhNmbmVw/0oDnEsSfd4KiJgyf8Hfd9wOBhw+xZEVzHJ+D98ixChH5RB1CyFDpK1qrU9+K7GhsRFEQQdIkr4Xv6ZKnQfcB/uuwQ9Po1vFyfZRmRbRrcYFWIhJSoBr7YY5Kn5LXJxpWPDLZGNe7zibE11J09gXBRXB2Oni2G7TEsNiMlY2w4SA4aBpVngyitw9So7hzwSPX+JCYZnHr3nBCDvkWaECvju9i6pclnuuEN4PFXghaxy3QOweWA4l1DF1jrgdQ+XU8VJk4H,iv:ZdGsr5AldRJYvoG+tXW2cnS7iEs4C14qjp7hQRNdKcI=,tag:fC/DbuFeSWA0vtn9fTV7bg==,type:str]
nuc-nixos-key: ENC[AES256_GCM,data:7VhntGkRDoXulB9FNelvF0YwxriuVCpUCb43V33LlCRI8dizGIGtayr4g4hwg88rdgGBZMIzpG0MrR65DhLriR+yJAuvgHBuNHb/IOt9x0Jjo4a5g9r4wwMjcj8TBFM0FiFh5oSysY/S6VYJif7aBTnqGWvrwyfWIuDOD7MWfQ==,iv:rVlATexxX7k9jrk6i11+Wgy6GhWKxTYkBBlGAqOAtMQ=,tag:YHELFmDNyHq1amjYQgTWTQ==,type:str] nuc-nixos-key: ENC[AES256_GCM,data:7VhntGkRDoXulB9FNelvF0YwxriuVCpUCb43V33LlCRI8dizGIGtayr4g4hwg88rdgGBZMIzpG0MrR65DhLriR+yJAuvgHBuNHb/IOt9x0Jjo4a5g9r4wwMjcj8TBFM0FiFh5oSysY/S6VYJif7aBTnqGWvrwyfWIuDOD7MWfQ==,iv:rVlATexxX7k9jrk6i11+Wgy6GhWKxTYkBBlGAqOAtMQ=,tag:YHELFmDNyHq1amjYQgTWTQ==,type:str]
ca-cert: ENC[AES256_GCM,data:ZXmAZdQ0BSGJB5IZ2VJx9IxrrNTqmEYGYKua0gk61/EOnebMp5yg+swKl94+pmFWtqwKlaH+jaChAqYchONHGxOt55AAAlhGzM7BzoUseWdjTf0mFRq4Kr49Tjsz1iOK5XHr8aLESF2E3RkBRi5r0MzstutuagSO59Dj74ZT176sMYWiT7yjPXBgxlLuROQGHBV1/l+N8AMt9M2OLp/0+QYcwSrDh3u8Ts82d9YMODcbNbnCaeo64xmHLW7jJBkDTeH89rfWA5hE+haqUDv/BWe1mBvkV0YIJceyfPZdku0+hOdUIw1iXOTH9Q3KTL5FR9i7lRrCz/ZJzOVrNA==,iv:Td7TBKn+5/1V3WblVwaWjYTOZXMvJ/SMSoUnrNXdAOU=,tag:6YDpTM9kADs2KHKERNeVFQ==,type:str] ca-cert: ENC[AES256_GCM,data:ZXmAZdQ0BSGJB5IZ2VJx9IxrrNTqmEYGYKua0gk61/EOnebMp5yg+swKl94+pmFWtqwKlaH+jaChAqYchONHGxOt55AAAlhGzM7BzoUseWdjTf0mFRq4Kr49Tjsz1iOK5XHr8aLESF2E3RkBRi5r0MzstutuagSO59Dj74ZT176sMYWiT7yjPXBgxlLuROQGHBV1/l+N8AMt9M2OLp/0+QYcwSrDh3u8Ts82d9YMODcbNbnCaeo64xmHLW7jJBkDTeH89rfWA5hE+haqUDv/BWe1mBvkV0YIJceyfPZdku0+hOdUIw1iXOTH9Q3KTL5FR9i7lRrCz/ZJzOVrNA==,iv:Td7TBKn+5/1V3WblVwaWjYTOZXMvJ/SMSoUnrNXdAOU=,tag:6YDpTM9kADs2KHKERNeVFQ==,type:str]
caddy:
cloudflare-dns-api-token: ENC[AES256_GCM,data:QMbo5KFej5MVIpWeWr+B4msS1dUPTtinKNJER44FPiKMNiFzkfGa4w==,iv:h18CzpdvPNOx/IWKEyuNlGmltdBeTUx4i8VMs0Dz8z0=,tag:Ke8nakp2VA/YSAH5Mvf9sQ==,type:str]
sops: sops:
shamir_threshold: 1 shamir_threshold: 1
age: age:
@@ -153,8 +155,8 @@ sops:
WFJONHNsUjJuditvVEgxQ0Y1RVhXQ1kKwBM8ljdCTTbjdasCdtLj4wZ+fX2XQIXf WFJONHNsUjJuditvVEgxQ0Y1RVhXQ1kKwBM8ljdCTTbjdasCdtLj4wZ+fX2XQIXf
IMgacJ5kxYHaYpNpY5wyK2kHzPY9Ovz75WyXicPj0SCojhoKvMAWXQ== IMgacJ5kxYHaYpNpY5wyK2kHzPY9Ovz75WyXicPj0SCojhoKvMAWXQ==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
lastmodified: "2026-03-24T14:25:23Z" lastmodified: "2026-04-09T19:46:52Z"
mac: ENC[AES256_GCM,data:H50AiSWZ3gzFw4EOfwQE2Z7D2Uj4ZqMbMdcOEY3umoA0wzN2JRBJuIU599tV+bRI4SqlJ/vsSDuoLEqxbDR1ZJfLTLzFxFOfr8kkj7bir1tkRWZPY6pkNBsIteWeaJktXyCodCRxaDuDiKNWlD3+tA5+X3Wjhg4RcAAQ+F18gkk=,iv:SffLSph1FL9Yg915VctG+TRJ/3aoJ3D1wBFiPS2MbAc=,tag:JQJ+rRPftBAeyKtUD+JQ2A==,type:str] mac: ENC[AES256_GCM,data:sQm090y8Txfg/DATQZxhYTPFSKGne5tk9534EOmddLQo4PWFRy5FdssCU7L9gCrcWy2x/hRPq57vNgOSvXrU/JNS3Q/lC2xR8G5cFiwFTg+efXoiS4dnJnAkO8i+IROuh3kbAy0rsECcW8yQS2A1aEXi52hfEkMFrCd9nfX0BmY=,iv:HdjMqLqcztzntb2ChVxcj91KcnP3jYFNhHR8ezgoOkk=,tag:4yPrEKw+I+ECAWrCq0M+2Q==,type:str]
pgp: pgp:
- created_at: "2026-02-06T15:34:31Z" - created_at: "2026-02-06T15:34:31Z"
enc: |- enc: |-
@@ -177,4 +179,4 @@ sops:
-----END PGP MESSAGE----- -----END PGP MESSAGE-----
fp: CBCB9B18A6B8930B0B6ABFD1CCB8CBEB30633684 fp: CBCB9B18A6B8930B0B6ABFD1CCB8CBEB30633684
unencrypted_suffix: _unencrypted unencrypted_suffix: _unencrypted
version: 3.12.1 version: 3.12.2

View File

@@ -64,6 +64,26 @@ in
security.tpm.enable = true; security.tpm.enable = true;
services = { services = {
caddy-internal = {
enable = true;
proxies = {
esphome = {
subdomain = "esphome";
upstream = "http://127.0.0.1:${toString net.ports.nuc.esphome}";
};
otbr = {
subdomain = "otbr";
upstream = "http://127.0.0.1:${toString net.ports.nuc.otbr}";
};
# hass is currently proxied by the NAS Caddy (modules/nixos/services/caddy).
# To migrate it here, remove the @hass block from that module and add:
# hass = {
# subdomain = "hass";
# upstream = "http://127.0.0.1:${toString net.ports.nuc.homeAssistant}";
# };
};
};
home-assistant = { home-assistant = {
enable = true; enable = true;
automation = { automation = {