nixos/nats: init

Changed files
+226
nixos
modules
services
networking
tests
+1
nixos/modules/module-list.nix
···
./services/networking/namecoind.nix
./services/networking/nar-serve.nix
./services/networking/nat.nix
+
./services/networking/nats.nix
./services/networking/ndppd.nix
./services/networking/nebula.nix
./services/networking/networkmanager.nix
+159
nixos/modules/services/networking/nats.nix
···
+
{ config, lib, pkgs, ... }:
+
+
with lib;
+
+
let
+
+
cfg = config.services.nats;
+
+
format = pkgs.formats.json { };
+
+
configFile = format.generate "nats.conf" cfg.settings;
+
+
in {
+
+
### Interface
+
+
options = {
+
services.nats = {
+
enable = mkEnableOption "NATS messaging system";
+
+
user = mkOption {
+
type = types.str;
+
default = "nats";
+
description = "User account under which NATS runs.";
+
};
+
+
group = mkOption {
+
type = types.str;
+
default = "nats";
+
description = "Group under which NATS runs.";
+
};
+
+
serverName = mkOption {
+
default = "nats";
+
example = "n1-c3";
+
type = types.str;
+
description = ''
+
Name of the NATS server, must be unique if clustered.
+
'';
+
};
+
+
jetstream = mkEnableOption "JetStream";
+
+
port = mkOption {
+
default = 4222;
+
example = 4222;
+
type = types.port;
+
description = ''
+
Port on which to listen.
+
'';
+
};
+
+
dataDir = mkOption {
+
default = "/var/lib/nats";
+
type = types.path;
+
description = ''
+
The NATS data directory. Only used if JetStream is enabled, for
+
storing stream metadata and messages.
+
+
If left as the default value this directory will automatically be
+
created before the NATS server starts, otherwise the sysadmin is
+
responsible for ensuring the directory exists with appropriate
+
ownership and permissions.
+
'';
+
};
+
+
settings = mkOption {
+
default = { };
+
type = format.type;
+
example = literalExample ''
+
{
+
jetstream = {
+
max_mem = "1G";
+
max_file = "10G";
+
};
+
};
+
'';
+
description = ''
+
Declarative NATS configuration. See the
+
<link xlink:href="https://docs.nats.io/nats-server/configuration">
+
NATS documentation</link> for a list of options.
+
'';
+
};
+
};
+
};
+
+
### Implementation
+
+
config = mkIf cfg.enable {
+
services.nats.settings = {
+
server_name = cfg.serverName;
+
port = cfg.port;
+
jetstream = optionalAttrs cfg.jetstream { store_dir = cfg.dataDir; };
+
};
+
+
systemd.services.nats = {
+
description = "NATS messaging system";
+
wantedBy = [ "multi-user.target" ];
+
after = [ "network.target" ];
+
+
serviceConfig = mkMerge [
+
(mkIf (cfg.dataDir == "/var/lib/nats") {
+
StateDirectory = "nats";
+
StateDirectoryMode = "0750";
+
})
+
{
+
Type = "simple";
+
ExecStart = "${pkgs.nats-server}/bin/nats-server -c ${configFile}";
+
ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+
ExecStop = "${pkgs.coreutils}/bin/kill -SIGINT $MAINPID";
+
Restart = "on-failure";
+
+
User = cfg.user;
+
Group = cfg.group;
+
+
# Hardening
+
CapabilityBoundingSet = "";
+
LimitNOFILE = 800000; # JetStream requires 2 FDs open per stream.
+
LockPersonality = true;
+
MemoryDenyWriteExecute = true;
+
NoNewPrivileges = true;
+
PrivateDevices = true;
+
PrivateTmp = true;
+
PrivateUsers = true;
+
ProcSubset = "pid";
+
ProtectClock = true;
+
ProtectControlGroups = true;
+
ProtectHome = true;
+
ProtectHostname = true;
+
ProtectKernelLogs = true;
+
ProtectKernelModules = true;
+
ProtectKernelTunables = true;
+
ProtectProc = "invisible";
+
ProtectSystem = "strict";
+
ReadOnlyPaths = [ ];
+
ReadWritePaths = [ cfg.dataDir ];
+
RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
+
RestrictNamespaces = true;
+
RestrictRealtime = true;
+
RestrictSUIDSGID = true;
+
SystemCallFilter = [ "@system-service" "~@privileged" "~@resources" ];
+
UMask = "0077";
+
}
+
];
+
};
+
+
users.users = mkIf (cfg.user == "nats") {
+
nats = {
+
description = "NATS daemon user";
+
isSystemUser = true;
+
group = cfg.group;
+
home = cfg.dataDir;
+
};
+
};
+
+
users.groups = mkIf (cfg.group == "nats") { nats = { }; };
+
};
+
+
}
+1
nixos/tests/all-tests.nix
···
nat.firewall = handleTest ./nat.nix { withFirewall = true; };
nat.firewall-conntrack = handleTest ./nat.nix { withFirewall = true; withConntrackHelpers = true; };
nat.standalone = handleTest ./nat.nix { withFirewall = false; };
+
nats = handleTest ./nats.nix {};
navidrome = handleTest ./navidrome.nix {};
ncdns = handleTest ./ncdns.nix {};
ndppd = handleTest ./ndppd.nix {};
+65
nixos/tests/nats.nix
···
+
let
+
+
port = 4222;
+
username = "client";
+
password = "password";
+
topic = "foo.bar";
+
+
in import ./make-test-python.nix ({ pkgs, lib, ... }: {
+
name = "nats";
+
meta = with pkgs.lib; { maintainers = with maintainers; [ c0deaddict ]; };
+
+
nodes = let
+
client = { pkgs, ... }: {
+
environment.systemPackages = with pkgs; [ natscli ];
+
};
+
in {
+
server = { pkgs, ... }: {
+
networking.firewall.allowedTCPPorts = [ port ];
+
services.nats = {
+
inherit port;
+
enable = true;
+
settings = {
+
authorization = {
+
users = [{
+
user = username;
+
inherit password;
+
}];
+
};
+
};
+
};
+
};
+
+
client1 = client;
+
client2 = client;
+
};
+
+
testScript = let file = "/tmp/msg";
+
in ''
+
def nats_cmd(*args):
+
return (
+
"nats "
+
"--server=nats://server:${toString port} "
+
"--user=${username} "
+
"--password=${password} "
+
"{}"
+
).format(" ".join(args))
+
+
start_all()
+
server.wait_for_unit("nats.service")
+
+
client1.fail("test -f ${file}")
+
+
# Subscribe on topic on client1 and echo messages to file.
+
client1.execute("({} | tee ${file} &)".format(nats_cmd("sub", "--raw", "${topic}")))
+
+
# Give client1 some time to subscribe.
+
client1.execute("sleep 2")
+
+
# Publish message on client2.
+
client2.execute(nats_cmd("pub", "${topic}", "hello"))
+
+
# Check if message has been received.
+
client1.succeed("grep -q hello ${file}")
+
'';
+
})