switch to emqx
check / flake-check (push) Successful in 25s
deploy / deploy (push) Successful in 48s

This commit is contained in:
Lars Nolden
2026-07-02 15:19:59 +02:00
parent 08d9fc594e
commit 58155dc737
11 changed files with 366 additions and 512 deletions
+3 -57
View File
@@ -3,24 +3,8 @@
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:
siteBlock = host: site:
if site ? staticRoot then ''
${host} {
root * ${site.staticRoot}
@@ -35,40 +19,7 @@ let
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)} }
'';
else throw "site `${host}` needs one of: staticRoot, upstream";
in
{
options.services.gebos.caddy = {
@@ -82,13 +33,8 @@ in
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
);
extraConfig = lib.concatStringsSep "\n" (lib.mapAttrsToList siteBlock cfg.sites);
};
};
}