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