Skip to content

Commit 1c64cd7

Browse files
committed
add nats service and related tests
1 parent 949fc6d commit 1c64cd7

File tree

3 files changed

+332
-0
lines changed

3 files changed

+332
-0
lines changed

examples/nats/.test.sh

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#!/usr/bin/env bash
2+
set -ex
3+
4+
# Wait for NATS to be ready via monitoring endpoint
5+
timeout 20 bash -c 'until curl -sf http://nats-user:[email protected]:8222/healthz >/dev/null 2>&1; do sleep 0.5; done'
6+
7+
# Test: Verify server is responding with auth
8+
curl -f http://nats-user:[email protected]:8222/varz | grep -q '"server_name"'
9+
10+
# Test: Verify JetStream is enabled
11+
curl -f http://nats-user:[email protected]:8222/jsz | grep -q '"config"'
12+
13+
echo "NATS server is healthy with JetStream and authorization enabled!"

examples/nats/devenv.nix

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{ pkgs, ... }:
2+
3+
{
4+
services.nats = {
5+
enable = true;
6+
7+
# Enable HTTP monitoring (provides /healthz, /varz, /connz endpoints)
8+
monitoring.enable = true;
9+
10+
# Enable JetStream for persistence and streaming
11+
jetstream = {
12+
enable = true;
13+
maxMemory = "1G";
14+
maxFileStore = "10G";
15+
};
16+
17+
# Enable authorization
18+
authorization = {
19+
enable = true;
20+
user = "nats-user";
21+
password = "nats-pass";
22+
};
23+
};
24+
}

src/modules/services/nats.nix

Lines changed: 295 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,295 @@
1+
{ config, lib, pkgs, ... }:
2+
3+
with lib;
4+
5+
let
6+
cfg = config.services.nats;
7+
8+
# Generate NATS config file from settings (only if settings are provided)
9+
configFile = pkgs.writeText "nats.conf" (builtins.toJSON cfg.settings);
10+
11+
# Build command-line arguments
12+
buildArgs = concatStringsSep " " (
13+
[ "-a ${cfg.host}" ]
14+
++ [ "-p ${toString cfg.port}" ]
15+
++ optional (cfg.serverName != "") "-n ${cfg.serverName}"
16+
++ optional (cfg.clientAdvertise != "") "--client_advertise ${cfg.clientAdvertise}"
17+
++ optional cfg.jetstream.enable "-js"
18+
++ optional cfg.jetstream.enable "-sd ${config.env.DEVENV_STATE}/nats/jetstream"
19+
++ optional cfg.monitoring.enable "-m ${toString cfg.monitoring.port}"
20+
++ optional (cfg.logFile != "") "-l ${cfg.logFile}"
21+
++ optional cfg.debug "-D"
22+
++ optional cfg.trace "-V"
23+
++ optional (cfg.settings != { }) "-c ${configFile}"
24+
);
25+
in
26+
{
27+
options.services.nats = {
28+
enable = mkOption {
29+
type = types.bool;
30+
default = false;
31+
description = ''
32+
Whether to enable the NATS messaging server.
33+
34+
NATS is a simple, secure and high performance messaging
35+
system for cloud native applications, IoT messaging,
36+
and microservices architectures.
37+
'';
38+
};
39+
40+
package = mkOption {
41+
type = types.package;
42+
default = pkgs.nats-server;
43+
defaultText = literalExpression "pkgs.nats-server";
44+
description = ''
45+
Which NATS server package to use.
46+
'';
47+
};
48+
49+
host = mkOption {
50+
type = types.str;
51+
default = "127.0.0.1";
52+
example = "0.0.0.0";
53+
description = ''
54+
Network host to listen on for client connections.
55+
Set to "0.0.0.0" to listen on all interfaces.
56+
Default is localhost for security.
57+
'';
58+
};
59+
60+
port = mkOption {
61+
type = types.port;
62+
default = 4222;
63+
description = ''
64+
Port to listen on for client connections.
65+
Default NATS client port is 4222.
66+
'';
67+
};
68+
69+
serverName = mkOption {
70+
type = types.str;
71+
default = "";
72+
example = "nats-dev-1";
73+
description = ''
74+
Server name for identification in clusters.
75+
If empty, NATS will auto-generate a unique name.
76+
'';
77+
};
78+
79+
clientAdvertise = mkOption {
80+
type = types.str;
81+
default = "";
82+
example = "localhost:4222";
83+
description = ''
84+
Client URL to advertise to other servers in a cluster.
85+
Useful when running behind NAT or in containers.
86+
'';
87+
};
88+
89+
jetstream = {
90+
enable = mkEnableOption "JetStream persistence layer for streaming and queues";
91+
92+
maxMemory = mkOption {
93+
type = types.str;
94+
default = "1G";
95+
example = "512M";
96+
description = ''
97+
Maximum memory for in-memory streams.
98+
Use suffixes: K, M, G, T for sizes.
99+
'';
100+
};
101+
102+
maxFileStore = mkOption {
103+
type = types.str;
104+
default = "10G";
105+
example = "100G";
106+
description = ''
107+
Maximum disk space for file-based streams.
108+
Use suffixes: K, M, G, T for sizes.
109+
'';
110+
};
111+
};
112+
113+
monitoring = {
114+
enable = mkOption {
115+
type = types.bool;
116+
default = true;
117+
description = ''
118+
Enable HTTP monitoring endpoint.
119+
Provides /healthz, /varz, /connz, and other monitoring endpoints.
120+
Highly recommended for production deployments.
121+
'';
122+
};
123+
124+
port = mkOption {
125+
type = types.port;
126+
default = 8222;
127+
description = ''
128+
Port for HTTP monitoring endpoint.
129+
Access monitoring at http://host:port/varz
130+
'';
131+
};
132+
};
133+
134+
logFile = mkOption {
135+
type = types.str;
136+
default = "";
137+
example = "/var/log/nats-server.log";
138+
description = ''
139+
Path to log file. If empty, logs to stdout.
140+
Stdout is recommended for devenv as logs are captured by process manager.
141+
'';
142+
};
143+
144+
debug = mkOption {
145+
type = types.bool;
146+
default = false;
147+
description = ''
148+
Enable debug logging for troubleshooting.
149+
'';
150+
};
151+
152+
trace = mkOption {
153+
type = types.bool;
154+
default = false;
155+
description = ''
156+
Enable protocol tracing for deep debugging.
157+
Warning: Very verbose output.
158+
'';
159+
};
160+
161+
authorization = {
162+
enable = mkEnableOption "authorization for client connections";
163+
164+
user = mkOption {
165+
type = types.str;
166+
default = "";
167+
example = "nats-user";
168+
description = ''
169+
Username required for client connections.
170+
Only used if authorization is enabled.
171+
'';
172+
};
173+
174+
password = mkOption {
175+
type = types.str;
176+
default = "";
177+
example = "nats-pass";
178+
description = ''
179+
Password required for client connections.
180+
Only used if authorization is enabled.
181+
Warning: This will be visible in the Nix store.
182+
'';
183+
};
184+
185+
token = mkOption {
186+
type = types.str;
187+
default = "";
188+
example = "my-secret-token";
189+
description = ''
190+
Token required for client connections.
191+
Alternative to user/password authentication.
192+
Warning: This will be visible in the Nix store.
193+
'';
194+
};
195+
};
196+
197+
settings = mkOption {
198+
type = types.attrs;
199+
default = { };
200+
example = literalExpression ''
201+
{
202+
tls = {
203+
cert_file = "/path/to/cert.pem";
204+
key_file = "/path/to/key.pem";
205+
verify = true;
206+
};
207+
cluster = {
208+
name = "my-cluster";
209+
listen = "0.0.0.0:6222";
210+
routes = [
211+
"nats://node1:6222"
212+
"nats://node2:6222"
213+
];
214+
};
215+
}
216+
'';
217+
description = ''
218+
Additional NATS server configuration as a Nix attribute set.
219+
This will be converted to NATS config file format.
220+
221+
Use this for advanced features like:
222+
- TLS/SSL configuration
223+
- Clustering with routes
224+
- MQTT gateway
225+
- WebSocket support
226+
- Custom authorization
227+
228+
See https://docs.nats.io/running-a-nats-service/configuration
229+
'';
230+
};
231+
};
232+
233+
config = mkIf cfg.enable {
234+
packages = [ cfg.package ];
235+
236+
# Merge basic options into settings for config file generation
237+
# Note: User-provided cfg.settings will be automatically merged by the module system
238+
services.nats.settings = mkMerge [
239+
# JetStream settings (if enabled)
240+
(mkIf cfg.jetstream.enable {
241+
jetstream = {
242+
store_dir = config.env.DEVENV_STATE + "/nats/jetstream";
243+
max_memory_store = cfg.jetstream.maxMemory;
244+
max_file_store = cfg.jetstream.maxFileStore;
245+
};
246+
})
247+
248+
# Authorization settings (if enabled)
249+
(mkIf cfg.authorization.enable (
250+
{
251+
authorization = { } //
252+
(optionalAttrs (cfg.authorization.user != "") { user = cfg.authorization.user; }) //
253+
(optionalAttrs (cfg.authorization.password != "") { password = cfg.authorization.password; }) //
254+
(optionalAttrs (cfg.authorization.token != "") { token = cfg.authorization.token; });
255+
}
256+
))
257+
];
258+
259+
env.NATS_DATA_DIR = config.env.DEVENV_STATE + "/nats";
260+
261+
# Create necessary directories
262+
enterShell = ''
263+
# Create NATS data directory
264+
mkdir -p ${config.env.DEVENV_STATE}/nats
265+
266+
# Create JetStream directory if enabled
267+
${optionalString cfg.jetstream.enable ''
268+
mkdir -p ${config.env.DEVENV_STATE}/nats/jetstream
269+
''}
270+
'';
271+
272+
processes.nats = {
273+
exec = "${cfg.package}/bin/nats-server ${buildArgs}";
274+
275+
process-compose = {
276+
readiness_probe = {
277+
# Use HTTP healthz endpoint if monitoring is enabled, otherwise TCP check
278+
exec.command =
279+
if cfg.monitoring.enable then
280+
"${pkgs.curl}/bin/curl -f http://${cfg.host}:${toString cfg.monitoring.port}/healthz"
281+
else
282+
"${pkgs.netcat}/bin/nc -z ${cfg.host} ${toString cfg.port}";
283+
initial_delay_seconds = 2;
284+
period_seconds = 5;
285+
timeout_seconds = 3;
286+
success_threshold = 1;
287+
failure_threshold = 5;
288+
};
289+
290+
# https://github.com/F1bonacc1/process-compose#-auto-restart-if-not-healthy
291+
availability.restart = "on_failure";
292+
};
293+
};
294+
};
295+
}

0 commit comments

Comments
 (0)