Skip to content

Commit 1d8a731

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

File tree

3 files changed

+331
-0
lines changed

3 files changed

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

0 commit comments

Comments
 (0)