08d9fc594e
The NixOS caddy module already emits one global options block (for `email`)
and appends extraConfig after it. gebos-caddy also prepended its own keyless
`{ layer4 { ... } }` block, so the rendered Caddyfile had two keyless blocks
and caddy rejected it: "server block without any key is global configuration,
and if used, it must be first" -> caddy.service failed -> mqtt-ingest
activation exited 4 and deploy-rs rolled back.
Move the layer4 directive into services.caddy.globalConfig so it lands inside
the module's single global block. Verified the rendered Caddyfile for
mqtt-ingest; app-host (http-only) output is unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
95 lines
3.3 KiB
Nix
95 lines
3.3 KiB
Nix
{ config, lib, pkgs, ... }:
|
|
|
|
let
|
|
cfg = config.services.gebos.caddy;
|
|
|
|
tcpProxySites = lib.filterAttrs (_: s: s ? tcpProxy) cfg.sites;
|
|
httpSites = lib.filterAttrs (_: s: !(s ? tcpProxy)) cfg.sites;
|
|
needsLayer4 = tcpProxySites != { };
|
|
|
|
# External MQTTS port the senders connect to (mqtt_port = 8883, SSL).
|
|
tlsListenPort = 8883;
|
|
|
|
# Caddy built with the layer4 module (caddy-l4) — needed to TLS-terminate raw
|
|
# MQTT and proxy it to HiveMQ, which Caddy's HTTP-only core can't do. Only
|
|
# built when a host actually has a tcpProxy site; app-host's static/upstream
|
|
# sites run on stock Caddy so they don't pay for the custom build.
|
|
caddyWithL4 = pkgs.caddy.withPlugins {
|
|
plugins = [ "github.com/mholt/caddy-l4@v0.1.1" ];
|
|
hash = "sha256-O6GuC2q1mA/Fa0utb2Yg7ZE73iq13oVYhJI1IVyOvog=";
|
|
};
|
|
|
|
# HTTP-style sites: static file server or reverse proxy.
|
|
httpSiteBlock = host: site:
|
|
if site ? staticRoot then ''
|
|
${host} {
|
|
root * ${site.staticRoot}
|
|
file_server
|
|
try_files {path} /index.html
|
|
encode zstd gzip
|
|
}
|
|
''
|
|
else if site ? upstream then ''
|
|
${host} {
|
|
reverse_proxy ${site.upstream}
|
|
encode zstd gzip
|
|
}
|
|
''
|
|
else throw "site `${host}` needs one of: staticRoot, upstream, tcpProxy";
|
|
|
|
# A tcpProxy site needs TWO Caddyfile pieces:
|
|
# 1. a layer4 listener (in the global options block) that terminates TLS on
|
|
# :8883 and proxies cleartext to the upstream (HiveMQ on 127.0.0.1:1883);
|
|
# 2. a normal HTTPS site block for the host, purely so Caddy's automatic
|
|
# HTTPS OBTAINS the ACME cert that the layer4 `tls` handler then serves by
|
|
# SNI — caddy-l4's `tls` handler only terminates, it never provisions
|
|
# certs. This is why ports 80/443 must stay open for the ACME challenge.
|
|
layer4Listener = _host: site: ''
|
|
:${toString tlsListenPort} {
|
|
route {
|
|
tls
|
|
proxy ${site.tcpProxy}
|
|
}
|
|
}
|
|
'';
|
|
|
|
certSiteBlock = host: _site: ''
|
|
${host} {
|
|
respond "gebos MQTT ingest — connect MQTTS on :${toString tlsListenPort}" 200
|
|
}
|
|
'';
|
|
|
|
# caddy-l4 listeners, folded into one `layer4` directive that lives INSIDE the
|
|
# Caddyfile global options block. We must NOT emit our own `{ }` block here:
|
|
# the NixOS caddy module already generates the single global block (for
|
|
# `email`), and a second keyless block is invalid ("server block without any
|
|
# key is global configuration, and if used, it must be first"). So this goes to
|
|
# services.caddy.globalConfig, which the module places inside that one block.
|
|
layer4Global = lib.optionalString needsLayer4 ''
|
|
layer4 {
|
|
${lib.concatStrings (lib.mapAttrsToList layer4Listener tcpProxySites)} }
|
|
'';
|
|
in
|
|
{
|
|
options.services.gebos.caddy = {
|
|
enable = lib.mkEnableOption "Gebos Caddy reverse proxy / TLS terminator";
|
|
sites = lib.mkOption {
|
|
type = lib.types.attrsOf lib.types.attrs;
|
|
default = { };
|
|
};
|
|
};
|
|
|
|
config = lib.mkIf cfg.enable {
|
|
services.caddy = {
|
|
enable = true;
|
|
package = if needsLayer4 then caddyWithL4 else pkgs.caddy;
|
|
email = "ops@gebos.online"; # TODO: confirm ACME contact
|
|
globalConfig = layer4Global;
|
|
extraConfig = lib.concatStringsSep "\n" (
|
|
lib.mapAttrsToList httpSiteBlock httpSites
|
|
++ lib.mapAttrsToList certSiteBlock tcpProxySites
|
|
);
|
|
};
|
|
};
|
|
}
|