diff --git a/nixos/modules/misc/ids.nix b/nixos/modules/misc/ids.nix
index 28ed10a5ece6..c0c6a6ef9244 100644
--- a/nixos/modules/misc/ids.nix
+++ b/nixos/modules/misc/ids.nix
@@ -303,6 +303,7 @@
restya-board = 284;
mighttpd2 = 285;
hass = 286;
+ monero = 287;
# When adding a uid, make sure it doesn't match an existing gid. And don't use uids above 399!
@@ -574,6 +575,7 @@
restya-board = 284;
mighttpd2 = 285;
hass = 286;
+ monero = 287;
# When adding a gid, make sure it doesn't match an existing
# uid. Users and groups with the same name should have equal
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index d9d80bcff818..91dd0529d3c8 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -492,6 +492,7 @@
./services/networking/minidlna.nix
./services/networking/miniupnpd.nix
./services/networking/mosquitto.nix
+ ./services/networking/monero.nix
./services/networking/miredo.nix
./services/networking/mstpd.nix
./services/networking/murmur.nix
diff --git a/nixos/modules/services/networking/monero.nix b/nixos/modules/services/networking/monero.nix
new file mode 100644
index 000000000000..31379189f5de
--- /dev/null
+++ b/nixos/modules/services/networking/monero.nix
@@ -0,0 +1,238 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.monero;
+ dataDir = "/var/lib/monero";
+
+ listToConf = option: list:
+ concatMapStrings (value: "${option}=${value}\n") list;
+
+ login = (cfg.rpc.user != null && cfg.rpc.password != null);
+
+ configFile = with cfg; pkgs.writeText "monero.conf" ''
+ log-file=/dev/stdout
+ data-dir=${dataDir}
+
+ ${optionalString mining.enable ''
+ start-mining=${mining.address}
+ mining-threads=${toString mining.threads}
+ ''}
+
+ rpc-bind-ip=${rpc.address}
+ rpc-bind-port=${toString rpc.port}
+ ${optionalString login ''
+ rpc-login=${rpc.user}:${rpc.password}
+ ''}
+ ${optionalString rpc.restricted ''
+ restrict-rpc=1
+ ''}
+
+ limit-rate-up=${toString limits.upload}
+ limit-rate-down=${toString limits.download}
+ max-concurrency=${toString limits.threads}
+ block-sync-size=${toString limits.syncSize}
+
+ ${listToConf "add-peer" extraNodes}
+ ${listToConf "add-priority-node" priorityNodes}
+ ${listToConf "add-exclusive-node" exclusiveNodes}
+
+ ${extraConfig}
+ '';
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.monero = {
+
+ enable = mkEnableOption "Monero node daemon.";
+
+ mining.enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to mine moneroj.
+ '';
+ };
+
+ mining.address = mkOption {
+ type = types.str;
+ default = "";
+ description = ''
+ Monero address where to send mining rewards.
+ '';
+ };
+
+ mining.threads = mkOption {
+ type = types.addCheck types.int (x: x>=0);
+ default = 0;
+ description = ''
+ Number of threads used for mining.
+ Set to 0 to use all available.
+ '';
+ };
+
+ rpc.user = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ User name for RPC connections.
+ '';
+ };
+
+ rpc.password = mkOption {
+ type = types.str;
+ default = null;
+ description = ''
+ Password for RPC connections.
+ '';
+ };
+
+ rpc.address = mkOption {
+ type = types.str;
+ default = "127.0.0.1";
+ description = ''
+ IP address the RPC server will bind to.
+ '';
+ };
+
+ rpc.port = mkOption {
+ type = types.int;
+ default = 18081;
+ description = ''
+ Port the RPC server will bind to.
+ '';
+ };
+
+ rpc.restricted = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to restrict RPC to view only commands.
+ '';
+ };
+
+ limits.upload = mkOption {
+ type = types.addCheck types.int (x: x>=-1);
+ default = -1;
+ description = ''
+ Limit of the upload rate in kB/s.
+ Set to -1 to leave unlimited.
+ '';
+ };
+
+ limits.download = mkOption {
+ type = types.addCheck types.int (x: x>=-1);
+ default = -1;
+ description = ''
+ Limit of the download rate in kB/s.
+ Set to -1 to leave unlimited.
+ '';
+ };
+
+ limits.threads = mkOption {
+ type = types.addCheck types.int (x: x>=0);
+ default = 0;
+ description = ''
+ Maximum number of threads used for a parallel job.
+ Set to 0 to leave unlimited.
+ '';
+ };
+
+ limits.syncSize = mkOption {
+ type = types.addCheck types.int (x: x>=0);
+ default = 0;
+ description = ''
+ Maximum number of blocks to sync at once.
+ Set to 0 for adaptive.
+ '';
+ };
+
+ extraNodes = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ description = ''
+ List of additional peer IP addresses to add to the local list.
+ '';
+ };
+
+ priorityNodes = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ description = ''
+ List of peer IP addresses to connect to and
+ attempt to keep the connection open.
+ '';
+ };
+
+ exclusiveNodes = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ description = ''
+ List of peer IP addresses to connect to *only*.
+ If given the other peer options will be ignored.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Extra lines to be added verbatim to monerod configuration.
+ '';
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ users.extraUsers = singleton {
+ name = "monero";
+ uid = config.ids.uids.monero;
+ description = "Monero daemon user";
+ home = dataDir;
+ createHome = true;
+ };
+
+ users.extraGroups = singleton {
+ name = "monero";
+ gid = config.ids.gids.monero;
+ };
+
+ systemd.services.monero = {
+ description = "monero daemon";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+
+ serviceConfig = {
+ User = "monero";
+ Group = "monero";
+ ExecStart = "${pkgs.monero}/bin/monerod --config-file=${configFile} --non-interactive";
+ Restart = "always";
+ SuccessExitStatus = [ 0 1 ];
+ };
+ };
+
+ assertions = singleton {
+ assertion = cfg.mining.enable -> cfg.mining.address != "";
+ message = ''
+ You need a Monero address to receive mining rewards:
+ specify one using option monero.mining.address.
+ '';
+ };
+
+ };
+
+}
+