1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6
7 cfg = config.services.nats;
8
9 format = pkgs.formats.json { };
10
11 configFile = format.generate "nats.conf" cfg.settings;
12
13in {
14
15 ### Interface
16
17 options = {
18 services.nats = {
19 enable = mkEnableOption (lib.mdDoc "NATS messaging system");
20
21 user = mkOption {
22 type = types.str;
23 default = "nats";
24 description = lib.mdDoc "User account under which NATS runs.";
25 };
26
27 group = mkOption {
28 type = types.str;
29 default = "nats";
30 description = lib.mdDoc "Group under which NATS runs.";
31 };
32
33 serverName = mkOption {
34 default = "nats";
35 example = "n1-c3";
36 type = types.str;
37 description = lib.mdDoc ''
38 Name of the NATS server, must be unique if clustered.
39 '';
40 };
41
42 jetstream = mkEnableOption (lib.mdDoc "JetStream");
43
44 port = mkOption {
45 default = 4222;
46 type = types.port;
47 description = lib.mdDoc ''
48 Port on which to listen.
49 '';
50 };
51
52 dataDir = mkOption {
53 default = "/var/lib/nats";
54 type = types.path;
55 description = lib.mdDoc ''
56 The NATS data directory. Only used if JetStream is enabled, for
57 storing stream metadata and messages.
58
59 If left as the default value this directory will automatically be
60 created before the NATS server starts, otherwise the sysadmin is
61 responsible for ensuring the directory exists with appropriate
62 ownership and permissions.
63 '';
64 };
65
66 settings = mkOption {
67 default = { };
68 type = format.type;
69 example = literalExpression ''
70 {
71 jetstream = {
72 max_mem = "1G";
73 max_file = "10G";
74 };
75 };
76 '';
77 description = lib.mdDoc ''
78 Declarative NATS configuration. See the
79 [
80 NATS documentation](https://docs.nats.io/nats-server/configuration) for a list of options.
81 '';
82 };
83 };
84 };
85
86 ### Implementation
87
88 config = mkIf cfg.enable {
89 services.nats.settings = {
90 server_name = cfg.serverName;
91 port = cfg.port;
92 jetstream = optionalAttrs cfg.jetstream { store_dir = cfg.dataDir; };
93 };
94
95 systemd.services.nats = {
96 description = "NATS messaging system";
97 wantedBy = [ "multi-user.target" ];
98 after = [ "network.target" ];
99
100 serviceConfig = mkMerge [
101 (mkIf (cfg.dataDir == "/var/lib/nats") {
102 StateDirectory = "nats";
103 StateDirectoryMode = "0750";
104 })
105 {
106 Type = "simple";
107 ExecStart = "${pkgs.nats-server}/bin/nats-server -c ${configFile}";
108 ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
109 ExecStop = "${pkgs.coreutils}/bin/kill -SIGINT $MAINPID";
110 Restart = "on-failure";
111
112 User = cfg.user;
113 Group = cfg.group;
114
115 # Hardening
116 CapabilityBoundingSet = "";
117 LimitNOFILE = 800000; # JetStream requires 2 FDs open per stream.
118 LockPersonality = true;
119 MemoryDenyWriteExecute = true;
120 NoNewPrivileges = true;
121 PrivateDevices = true;
122 PrivateTmp = true;
123 PrivateUsers = true;
124 ProcSubset = "pid";
125 ProtectClock = true;
126 ProtectControlGroups = true;
127 ProtectHome = true;
128 ProtectHostname = true;
129 ProtectKernelLogs = true;
130 ProtectKernelModules = true;
131 ProtectKernelTunables = true;
132 ProtectProc = "invisible";
133 ProtectSystem = "strict";
134 ReadOnlyPaths = [ ];
135 ReadWritePaths = [ cfg.dataDir ];
136 RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
137 RestrictNamespaces = true;
138 RestrictRealtime = true;
139 RestrictSUIDSGID = true;
140 SystemCallFilter = [ "@system-service" "~@privileged" ];
141 UMask = "0077";
142 }
143 ];
144 };
145
146 users.users = mkIf (cfg.user == "nats") {
147 nats = {
148 description = "NATS daemon user";
149 isSystemUser = true;
150 group = cfg.group;
151 home = cfg.dataDir;
152 };
153 };
154
155 users.groups = mkIf (cfg.group == "nats") { nats = { }; };
156 };
157
158}