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