1{
2 config,
3 pkgs,
4 lib,
5 ...
6}:
7with lib;
8let
9 cfg = config.services.victoriametrics;
10 settingsFormat = pkgs.formats.yaml { };
11
12 startCLIList = [
13 "${cfg.package}/bin/victoria-metrics"
14 "-storageDataPath=/var/lib/${cfg.stateDir}"
15 "-httpListenAddr=${cfg.listenAddress}"
16
17 ]
18 ++ lib.optionals (cfg.retentionPeriod != null) [ "-retentionPeriod=${cfg.retentionPeriod}" ]
19 ++ cfg.extraOptions;
20 prometheusConfigYml = checkedConfig (
21 settingsFormat.generate "prometheusConfig.yaml" cfg.prometheusConfig
22 );
23
24 checkedConfig =
25 file:
26 if cfg.checkConfig then
27 pkgs.runCommand "checked-config" { nativeBuildInputs = [ cfg.package ]; } ''
28 ln -s ${file} $out
29 ${lib.escapeShellArgs startCLIList} -promscrape.config=${file} -dryRun
30 ''
31 else
32 file;
33in
34{
35 options.services.victoriametrics = {
36 enable = lib.mkOption {
37 type = lib.types.bool;
38 default = false;
39 description = ''
40 Whether to enable VictoriaMetrics in single-node mode.
41
42 VictoriaMetrics is a fast, cost-effective and scalable monitoring solution and time series database.
43 '';
44 };
45 package = mkPackageOption pkgs "victoriametrics" { };
46
47 listenAddress = mkOption {
48 default = ":8428";
49 type = types.str;
50 description = ''
51 TCP address to listen for incoming http requests.
52 '';
53 };
54
55 stateDir = mkOption {
56 type = types.str;
57 default = "victoriametrics";
58 description = ''
59 Directory below `/var/lib` to store VictoriaMetrics metrics data.
60 This directory will be created automatically using systemd's StateDirectory mechanism.
61 '';
62 };
63
64 retentionPeriod = mkOption {
65 type = types.nullOr types.str;
66 default = null;
67 example = "15d";
68 description = ''
69 How long to retain samples in storage.
70 The minimum retentionPeriod is 24h or 1d. See also -retentionFilter
71 The following optional suffixes are supported: s (second), h (hour), d (day), w (week), y (year).
72 If suffix isn't set, then the duration is counted in months (default 1)
73 '';
74 };
75
76 basicAuthUsername = lib.mkOption {
77 default = null;
78 type = lib.types.nullOr lib.types.str;
79 description = ''
80 Basic Auth username used to protect VictoriaMetrics instance by authorization
81 '';
82 };
83
84 basicAuthPasswordFile = lib.mkOption {
85 default = null;
86 type = lib.types.nullOr lib.types.path;
87 description = ''
88 File that contains the Basic Auth password used to protect VictoriaMetrics instance by authorization
89 '';
90 };
91
92 prometheusConfig = lib.mkOption {
93 type = lib.types.submodule { freeformType = settingsFormat.type; };
94 default = { };
95 example = literalExpression ''
96 {
97 scrape_configs = [
98 {
99 job_name = "postgres-exporter";
100 metrics_path = "/metrics";
101 static_configs = [
102 {
103 targets = ["1.2.3.4:9187"];
104 labels.type = "database";
105 }
106 ];
107 }
108 {
109 job_name = "node-exporter";
110 metrics_path = "/metrics";
111 static_configs = [
112 {
113 targets = ["1.2.3.4:9100"];
114 labels.type = "node";
115 }
116 {
117 targets = ["5.6.7.8:9100"];
118 labels.type = "node";
119 }
120 ];
121 }
122 ];
123 }
124 '';
125 description = ''
126 Config for prometheus style metrics.
127 See the docs: <https://docs.victoriametrics.com/vmagent/#how-to-collect-metrics-in-prometheus-format>
128 for more information.
129 '';
130 };
131
132 extraOptions = mkOption {
133 type = types.listOf types.str;
134 default = [ ];
135 example = literalExpression ''
136 [
137 "-loggerLevel=WARN"
138 ]
139 '';
140 description = ''
141 Extra options to pass to VictoriaMetrics. See the docs:
142 <https://docs.victoriametrics.com/single-server-victoriametrics/#list-of-command-line-flags>
143 or {command}`victoriametrics -help` for more information.
144 '';
145 };
146
147 checkConfig = lib.mkOption {
148 type = lib.types.bool;
149 default = true;
150 description = ''
151 Check configuration.
152
153 If you use credentials stored in external files (`environmentFile`, etc),
154 they will not be visible and it will report errors, despite a correct configuration.
155 '';
156 };
157 };
158
159 config = lib.mkIf cfg.enable {
160
161 assertions = [
162 {
163 assertion =
164 (cfg.basicAuthUsername == null && cfg.basicAuthPasswordFile == null)
165 || (cfg.basicAuthUsername != null && cfg.basicAuthPasswordFile != null);
166 message = "Both basicAuthUsername and basicAuthPasswordFile must be set together to enable basicAuth functionality, or neither should be set.";
167 }
168 ];
169
170 systemd.services.victoriametrics = {
171 description = "VictoriaMetrics time series database";
172 wantedBy = [ "multi-user.target" ];
173 after = [ "network.target" ];
174 startLimitBurst = 5;
175
176 serviceConfig = {
177 ExecStart = lib.escapeShellArgs (
178 startCLIList
179 ++ lib.optionals (cfg.prometheusConfig != { }) [ "-promscrape.config=${prometheusConfigYml}" ]
180 ++ lib.optional (cfg.basicAuthUsername != null) "-httpAuth.username=${cfg.basicAuthUsername}"
181 ++ lib.optional (
182 cfg.basicAuthPasswordFile != null
183 ) "-httpAuth.password=file://%d/basic_auth_password"
184 );
185
186 DynamicUser = true;
187 LoadCredential = lib.optionals (cfg.basicAuthPasswordFile != null) [
188 "basic_auth_password:${cfg.basicAuthPasswordFile}"
189 ];
190
191 RestartSec = 1;
192 Restart = "on-failure";
193 RuntimeDirectory = "victoriametrics";
194 RuntimeDirectoryMode = "0700";
195 StateDirectory = cfg.stateDir;
196 StateDirectoryMode = "0700";
197
198 # Increase the limit to avoid errors like 'too many open files' when merging small parts
199 LimitNOFILE = 1048576;
200
201 # Hardening
202 DeviceAllow = [ "/dev/null rw" ];
203 DevicePolicy = "strict";
204 LockPersonality = true;
205 MemoryDenyWriteExecute = true;
206 NoNewPrivileges = true;
207 PrivateDevices = true;
208 PrivateTmp = true;
209 PrivateUsers = true;
210 ProtectClock = true;
211 ProtectControlGroups = true;
212 ProtectHome = true;
213 ProtectHostname = true;
214 ProtectKernelLogs = true;
215 ProtectKernelModules = true;
216 ProtectKernelTunables = true;
217 ProtectProc = "invisible";
218 ProtectSystem = "full";
219 RemoveIPC = true;
220 RestrictAddressFamilies = [
221 "AF_INET"
222 "AF_INET6"
223 "AF_UNIX"
224 ];
225 RestrictNamespaces = true;
226 RestrictRealtime = true;
227 RestrictSUIDSGID = true;
228 SystemCallArchitectures = "native";
229 SystemCallFilter = [
230 "@system-service"
231 "~@privileged"
232 ];
233 };
234
235 postStart =
236 let
237 bindAddr =
238 (lib.optionalString (lib.hasPrefix ":" cfg.listenAddress) "127.0.0.1") + cfg.listenAddress;
239 in
240 lib.mkBefore ''
241 until ${lib.getBin pkgs.curl}/bin/curl -s -o /dev/null http://${bindAddr}/ping; do
242 sleep 1;
243 done
244 '';
245 };
246 };
247}