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 limit-rate-up=${toString limits.upload}
36 limit-rate-down=${toString limits.download}
37 max-concurrency=${toString limits.threads}
38 block-sync-size=${toString limits.syncSize}
39
40 ${listToConf "add-peer" extraNodes}
41 ${listToConf "add-priority-node" priorityNodes}
42 ${listToConf "add-exclusive-node" exclusiveNodes}
43
44 ${extraConfig}
45 '';
46
47in
48
49{
50
51 ###### interface
52
53 options = {
54
55 services.monero = {
56
57 enable = lib.mkEnableOption "Monero node daemon";
58
59 dataDir = lib.mkOption {
60 type = lib.types.str;
61 default = "/var/lib/monero";
62 description = ''
63 The directory where Monero stores its data files.
64 '';
65 };
66
67 mining.enable = lib.mkOption {
68 type = lib.types.bool;
69 default = false;
70 description = ''
71 Whether to mine monero.
72 '';
73 };
74
75 mining.address = lib.mkOption {
76 type = lib.types.str;
77 default = "";
78 description = ''
79 Monero address where to send mining rewards.
80 '';
81 };
82
83 mining.threads = lib.mkOption {
84 type = lib.types.addCheck lib.types.int (x: x >= 0);
85 default = 0;
86 description = ''
87 Number of threads used for mining.
88 Set to `0` to use all available.
89 '';
90 };
91
92 rpc.user = lib.mkOption {
93 type = lib.types.nullOr lib.types.str;
94 default = null;
95 description = ''
96 User name for RPC connections.
97 '';
98 };
99
100 rpc.password = lib.mkOption {
101 type = lib.types.nullOr lib.types.str;
102 default = null;
103 description = ''
104 Password for RPC connections.
105 '';
106 };
107
108 rpc.address = lib.mkOption {
109 type = lib.types.str;
110 default = "127.0.0.1";
111 description = ''
112 IP address the RPC server will bind to.
113 '';
114 };
115
116 rpc.port = lib.mkOption {
117 type = lib.types.port;
118 default = 18081;
119 description = ''
120 Port the RPC server will bind to.
121 '';
122 };
123
124 rpc.restricted = lib.mkOption {
125 type = lib.types.bool;
126 default = false;
127 description = ''
128 Whether to restrict RPC to view only commands.
129 '';
130 };
131
132 limits.upload = lib.mkOption {
133 type = lib.types.addCheck lib.types.int (x: x >= -1);
134 default = -1;
135 description = ''
136 Limit of the upload rate in kB/s.
137 Set to `-1` to leave unlimited.
138 '';
139 };
140
141 limits.download = lib.mkOption {
142 type = lib.types.addCheck lib.types.int (x: x >= -1);
143 default = -1;
144 description = ''
145 Limit of the download rate in kB/s.
146 Set to `-1` to leave unlimited.
147 '';
148 };
149
150 limits.threads = lib.mkOption {
151 type = lib.types.addCheck lib.types.int (x: x >= 0);
152 default = 0;
153 description = ''
154 Maximum number of threads used for a parallel job.
155 Set to `0` to leave unlimited.
156 '';
157 };
158
159 limits.syncSize = lib.mkOption {
160 type = lib.types.addCheck lib.types.int (x: x >= 0);
161 default = 0;
162 description = ''
163 Maximum number of blocks to sync at once.
164 Set to `0` for adaptive.
165 '';
166 };
167
168 extraNodes = lib.mkOption {
169 type = lib.types.listOf lib.types.str;
170 default = [ ];
171 description = ''
172 List of additional peer IP addresses to add to the local list.
173 '';
174 };
175
176 priorityNodes = lib.mkOption {
177 type = lib.types.listOf lib.types.str;
178 default = [ ];
179 description = ''
180 List of peer IP addresses to connect to and
181 attempt to keep the connection open.
182 '';
183 };
184
185 exclusiveNodes = lib.mkOption {
186 type = lib.types.listOf lib.types.str;
187 default = [ ];
188 description = ''
189 List of peer IP addresses to connect to *only*.
190 If given the other peer options will be ignored.
191 '';
192 };
193
194 extraConfig = lib.mkOption {
195 type = lib.types.lines;
196 default = "";
197 description = ''
198 Extra lines to be added verbatim to monerod configuration.
199 '';
200 };
201
202 };
203
204 };
205
206 ###### implementation
207
208 config = lib.mkIf cfg.enable {
209
210 users.users.monero = {
211 isSystemUser = true;
212 group = "monero";
213 description = "Monero daemon user";
214 home = cfg.dataDir;
215 createHome = true;
216 };
217
218 users.groups.monero = { };
219
220 systemd.services.monero = {
221 description = "monero daemon";
222 after = [ "network.target" ];
223 wantedBy = [ "multi-user.target" ];
224
225 serviceConfig = {
226 User = "monero";
227 Group = "monero";
228 ExecStart = "${pkgs.monero-cli}/bin/monerod --config-file=${configFile} --non-interactive";
229 Restart = "always";
230 SuccessExitStatus = [
231 0
232 1
233 ];
234 };
235 };
236
237 assertions = lib.singleton {
238 assertion = cfg.mining.enable -> cfg.mining.address != "";
239 message = ''
240 You need a Monero address to receive mining rewards:
241 specify one using option monero.mining.address.
242 '';
243 };
244
245 };
246
247 meta.maintainers = with lib.maintainers; [ rnhmjoj ];
248
249}