snid: add modular service and test

This commit is contained in:
Tom Fitzhenry
2026-04-08 09:26:22 +00:00
parent 858f9eac66
commit 94fa9d4b65
4 changed files with 214 additions and 0 deletions

View File

@@ -1477,6 +1477,7 @@ in
smokeping = runTest ./smokeping.nix;
snapcast = runTest ./snapcast.nix;
snapper = runTest ./snapper.nix;
snid = runTest ./snid.nix;
snipe-it = runTest ./web-apps/snipe-it.nix;
snips-sh = runTest ./snips-sh.nix;
snmpd = runTest ./snmpd.nix;

31
nixos/tests/snid.nix Normal file
View File

@@ -0,0 +1,31 @@
{ lib, ... }:
{
_class = "nixosTest";
name = "snid";
nodes = {
machine =
{ pkgs, ... }:
{
system.services.snid = {
imports = [ pkgs.snid.services.default ];
snid = {
listen = [ "tcp:8443" ];
mode = "tcp";
backendCidrs = [ "127.0.0.0/8" ];
};
};
networking.firewall.allowedTCPPorts = [ 8443 ];
};
};
testScript = ''
start_all()
machine.wait_for_unit("multi-user.target")
machine.wait_for_unit("snid.service")
machine.wait_for_open_port(8443)
'';
meta.maintainers = with lib.maintainers; [ tomfitzhenry ];
}

View File

@@ -2,6 +2,7 @@
lib,
buildGoModule,
fetchFromGitHub,
nixosTests,
}:
buildGoModule (finalAttrs: {
@@ -17,6 +18,14 @@ buildGoModule (finalAttrs: {
vendorHash = "sha256-cVarG6Tx4yWpZE5BLZsMtLV9LF1lsiFfIXxhYiNjQlY=";
passthru = {
tests.nixos = nixosTests.snid;
services.default = {
imports = [ (lib.modules.importApply ./service.nix { }) ];
snid.package = finalAttrs.finalPackage;
};
};
meta = {
description = "Zero config TLS proxy server that uses SNI";
homepage = "https://github.com/AGWA/snid";

View File

@@ -0,0 +1,173 @@
# Non-module dependencies (`importApply`)
{ }:
# Service module
{
lib,
config,
options,
...
}:
let
inherit (lib)
concatMap
getExe
mkOption
optional
types
;
cfg = config.snid;
in
{
# https://nixos.org/manual/nixos/unstable/#modular-services
_class = "service";
options = {
snid = {
package = mkOption {
description = "Package to use for snid.";
defaultText = "The snid package that provided this module.";
type = types.package;
};
listen = mkOption {
description = ''
Addresses to listen on, in go-listener syntax.
Examples: `"tcp:443"`, `"tcp:0.0.0.0:443"`, `"tcp:192.0.2.4:443"`.
'';
type = types.listOf types.str;
example = [ "tcp:0.0.0.0:443" ];
};
mode = mkOption {
description = ''
Proxy mode. One of `nat46`, `tcp`, or `unix`.
'';
type = types.enum [
"nat46"
"tcp"
"unix"
];
};
defaultHostname = mkOption {
description = ''
Hostname to use if a client does not include the SNI extension.
If null, SNI-less connections will be terminated with a TLS alert.
'';
type = types.nullOr types.str;
default = null;
};
nat46Prefix = mkOption {
description = ''
IPv6 prefix for the source address when connecting to the backend
in NAT46 mode. The client's IPv4 address is placed in the lower 4
bytes.
Note: this prefix must be routed to the local host, e.g.
```
ip route add local 64:ff9b:1::/96 dev lo
```
'';
type = types.nullOr types.str;
default = null;
example = "64:ff9b:1::";
};
backendCidrs = mkOption {
description = ''
Subnets to which connections may be forwarded. Connections to
addresses outside these subnets are rejected. Used in `nat46` and
`tcp` modes.
'';
type = types.listOf types.str;
default = [ ];
example = [
"2001:db8::/64"
"192.0.2.0/24"
];
};
backendPort = mkOption {
description = ''
Port number to connect to on the backend in TCP mode. If null,
snid uses the same port as the inbound connection.
'';
type = types.nullOr types.port;
default = null;
};
unixDirectory = mkOption {
description = ''
Path to the directory containing UNIX domain sockets, used in
`unix` mode.
'';
type = types.nullOr types.path;
default = null;
};
proxyProto = mkOption {
description = ''
Use PROXY protocol v2 to convey the client IP address to the
backend. Applicable in `tcp` and `unix` modes.
'';
type = types.bool;
default = false;
};
};
};
config = {
assertions = [
{
assertion = cfg.mode == "nat46" -> cfg.nat46Prefix != null;
message = "snid: `nat46Prefix` must be set when `mode` is `nat46`.";
}
{
assertion = cfg.mode == "nat46" -> cfg.backendCidrs != [ ];
message = "snid: `backendCidrs` must be set when `mode` is `nat46`.";
}
{
assertion = cfg.mode == "tcp" -> cfg.backendCidrs != [ ];
message = "snid: `backendCidrs` must be set when `mode` is `tcp`.";
}
{
assertion = cfg.mode == "unix" -> cfg.unixDirectory != null;
message = "snid: `unixDirectory` must be set when `mode` is `unix`.";
}
];
process.argv = [
(getExe cfg.package)
"-mode"
cfg.mode
]
++ concatMap (l: [
"-listen"
l
]) cfg.listen
++ concatMap (c: [
"-backend-cidr"
c
]) cfg.backendCidrs
++ optional (cfg.defaultHostname != null) "-default-hostname=${cfg.defaultHostname}"
++ optional (cfg.nat46Prefix != null) "-nat46-prefix=${cfg.nat46Prefix}"
++ optional (cfg.backendPort != null) "-backend-port=${toString cfg.backendPort}"
++ optional (cfg.unixDirectory != null) "-unix-directory=${cfg.unixDirectory}"
++ optional cfg.proxyProto "-proxy-proto";
}
// lib.optionalAttrs (options ? systemd) {
systemd.service = {
after = [ "network.target" ];
wants = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Restart = "on-failure";
DynamicUser = true;
AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ];
};
};
};
}