1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7
8let
9 cfg = config.services.monero;
10
11 listToConf = option: list: lib.concatMapStrings (value: "${option}=${value}\n") list;
12
13 login = (cfg.rpc.user != null && cfg.rpc.password != null);
14
15 configFile =
16 with cfg;
17 pkgs.writeText "monero.conf" ''
18 log-file=/dev/stdout
19 data-dir=${dataDir}
20
21 ${lib.optionalString mining.enable ''
22 start-mining=${mining.address}
23 mining-threads=${toString mining.threads}
24 ''}
25
26 rpc-bind-ip=${rpc.address}
27 rpc-bind-port=${toString rpc.port}
28 ${lib.optionalString login ''
29 rpc-login=${rpc.user}:${rpc.password}
30 ''}
31 ${lib.optionalString rpc.restricted ''
32 restricted-rpc=1
33 ''}
34
35 ${lib.optionalString (banlist != null) ''
36 ban-list=${banlist}
37 ''}
38
39 limit-rate-up=${toString limits.upload}
40 limit-rate-down=${toString limits.download}
41 max-concurrency=${toString limits.threads}
42 block-sync-size=${toString limits.syncSize}
43
44 ${listToConf "add-peer" extraNodes}
45 ${listToConf "add-priority-node" priorityNodes}
46 ${listToConf "add-exclusive-node" exclusiveNodes}
47
48 ${lib.optionalString prune ''
49 prune-blockchain=1
50 sync-pruned-blocks=1
51 ''}
52
53 ${extraConfig}
54 '';
55
56in
57
58{
59
60 ###### interface
61
62 options = {
63
64 services.monero = {
65
66 enable = lib.mkEnableOption "Monero node daemon";
67
68 dataDir = lib.mkOption {
69 type = lib.types.str;
70 default = "/var/lib/monero";
71 description = ''
72 The directory where Monero stores its data files.
73 '';
74 };
75
76 banlist = lib.mkOption {
77 type = lib.types.nullOr lib.types.path;
78 default = null;
79 description = ''
80 Path to a text file containing IPs to block.
81 Useful to prevent DDoS/deanonymization attacks.
82
83 <https://github.com/monero-project/meta/issues/1124>
84 '';
85 example = lib.literalExpression ''
86 builtins.fetchurl {
87 url = "https://raw.githubusercontent.com/rblaine95/monero-banlist/c6eb9413ddc777e7072d822f49923df0b2a94d88/block.txt";
88 hash = "";
89 };
90 '';
91 };
92
93 mining.enable = lib.mkOption {
94 type = lib.types.bool;
95 default = false;
96 description = ''
97 Whether to mine monero.
98 '';
99 };
100
101 mining.address = lib.mkOption {
102 type = lib.types.str;
103 default = "";
104 description = ''
105 Monero address where to send mining rewards.
106 '';
107 };
108
109 mining.threads = lib.mkOption {
110 type = lib.types.ints.unsigned;
111 default = 0;
112 description = ''
113 Number of threads used for mining.
114 Set to `0` to use all available.
115 '';
116 };
117
118 rpc.user = lib.mkOption {
119 type = lib.types.nullOr lib.types.str;
120 default = null;
121 description = ''
122 User name for RPC connections.
123 '';
124 };
125
126 rpc.password = lib.mkOption {
127 type = lib.types.nullOr lib.types.str;
128 default = null;
129 description = ''
130 Password for RPC connections.
131 '';
132 };
133
134 rpc.address = lib.mkOption {
135 type = lib.types.str;
136 default = "127.0.0.1";
137 description = ''
138 IP address the RPC server will bind to.
139 '';
140 };
141
142 rpc.port = lib.mkOption {
143 type = lib.types.port;
144 default = 18081;
145 description = ''
146 Port the RPC server will bind to.
147 '';
148 };
149
150 rpc.restricted = lib.mkOption {
151 type = lib.types.bool;
152 default = false;
153 description = ''
154 Whether to restrict RPC to view only commands.
155 '';
156 };
157
158 limits.upload = lib.mkOption {
159 type = lib.types.addCheck lib.types.int (x: x >= -1);
160 default = -1;
161 description = ''
162 Limit of the upload rate in kB/s.
163 Set to `-1` to leave unlimited.
164 '';
165 };
166
167 limits.download = lib.mkOption {
168 type = lib.types.addCheck lib.types.int (x: x >= -1);
169 default = -1;
170 description = ''
171 Limit of the download rate in kB/s.
172 Set to `-1` to leave unlimited.
173 '';
174 };
175
176 limits.threads = lib.mkOption {
177 type = lib.types.ints.unsigned;
178 default = 0;
179 description = ''
180 Maximum number of threads used for a parallel job.
181 Set to `0` to leave unlimited.
182 '';
183 };
184
185 limits.syncSize = lib.mkOption {
186 type = lib.types.ints.unsigned;
187 default = 0;
188 description = ''
189 Maximum number of blocks to sync at once.
190 Set to `0` for adaptive.
191 '';
192 };
193
194 extraNodes = lib.mkOption {
195 type = lib.types.listOf lib.types.str;
196 default = [ ];
197 description = ''
198 List of additional peer IP addresses to add to the local list.
199 '';
200 };
201
202 priorityNodes = lib.mkOption {
203 type = lib.types.listOf lib.types.str;
204 default = [ ];
205 description = ''
206 List of peer IP addresses to connect to and
207 attempt to keep the connection open.
208 '';
209 };
210
211 exclusiveNodes = lib.mkOption {
212 type = lib.types.listOf lib.types.str;
213 default = [ ];
214 description = ''
215 List of peer IP addresses to connect to *only*.
216 If given the other peer options will be ignored.
217 '';
218 };
219
220 prune = lib.mkOption {
221 type = lib.types.bool;
222 default = false;
223 description = ''
224 Whether to prune the blockchain.
225 <https://www.getmonero.org/resources/moneropedia/pruning.html>
226 '';
227 };
228
229 environmentFile = lib.mkOption {
230 type = lib.types.nullOr lib.types.path;
231 default = null;
232 example = "/var/lib/monero/monerod.env";
233 description = ''
234 Path to an EnvironmentFile for the monero service as defined in {manpage}`systemd.exec(5)`.
235
236 Secrets may be passed to the service by specifying placeholder variables in the Nix config
237 and setting values in the environment file.
238
239 Example:
240
241 ```
242 # In environment file:
243 MINING_ADDRESS=888tNkZrPN6JsEgekjMnABU4TBzc2Dt29EPAvkRxbANsAnjyPbb3iQ1YBRk1UXcdRsiKc9dhwMVgN5S9cQUiyoogDavup3H
244 ```
245
246 ```
247 # Service config
248 services.monero.mining.address = "$MINING_ADDRESS";
249 ```
250 '';
251 };
252
253 extraConfig = lib.mkOption {
254 type = lib.types.lines;
255 default = "";
256 description = ''
257 Extra lines to be added verbatim to monerod configuration.
258 '';
259 };
260
261 };
262
263 };
264
265 ###### implementation
266
267 config = lib.mkIf cfg.enable {
268
269 users.users.monero = {
270 isSystemUser = true;
271 group = "monero";
272 description = "Monero daemon user";
273 home = cfg.dataDir;
274 createHome = true;
275 };
276
277 users.groups.monero = { };
278
279 systemd.services.monero = {
280 description = "monero daemon";
281 after = [ "network.target" ];
282 wantedBy = [ "multi-user.target" ];
283
284 preStart = ''
285 umask 077
286 ${pkgs.envsubst}/bin/envsubst \
287 -i ${configFile} \
288 -o ${cfg.dataDir}/monerod.conf
289 '';
290
291 serviceConfig = {
292 User = "monero";
293 Group = "monero";
294 EnvironmentFile = lib.mkIf (cfg.environmentFile != null) [ cfg.environmentFile ];
295 ExecStart = "${lib.getExe' pkgs.monero-cli "monerod"} --config-file=${cfg.dataDir}/monerod.conf --non-interactive";
296 Restart = "always";
297 SuccessExitStatus = [
298 0
299 1
300 ];
301 };
302 };
303
304 assertions = lib.singleton {
305 assertion = cfg.mining.enable -> cfg.mining.address != "";
306 message = ''
307 You need a Monero address to receive mining rewards:
308 specify one using option monero.mining.address.
309 '';
310 };
311
312 };
313
314 meta.maintainers = with lib.maintainers; [ rnhmjoj ];
315
316}