From 492eab0e26f7d2040bbb4c1550e502b837835af0 Mon Sep 17 00:00:00 2001 From: Jonas Nick Date: Sun, 14 Apr 2019 16:55:40 +0000 Subject: [PATCH] Add recurring donations module --- README.md | 1 + configuration.nix | 11 ++++ modules/default.nix | 1 + modules/nix-bitcoin.nix | 1 + modules/recurring-donations.nix | 108 ++++++++++++++++++++++++++++++++ 5 files changed, 122 insertions(+) create mode 100644 modules/recurring-donations.nix diff --git a/README.md b/README.md index 0f4a3c5..48bd4d1 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,7 @@ In `configuration.nix` the user can enable: * an index page using nginx to display node information and link to nanopos * [spark-wallet](https://github.com/shesek/spark-wallet) * [electrs](https://github.com/romanz/electrs) +* recurring-donations, a module to repeatedly send lightning payments to recipients specified in the configuration. The data directories of the services can be found in `/var/lib` on the deployed machines. diff --git a/configuration.nix b/configuration.nix index d644e81..cc2e1cf 100644 --- a/configuration.nix +++ b/configuration.nix @@ -58,6 +58,17 @@ # this if clightning, lightning-charge, and nanopos are enabled. # services.nix-bitcoin-webindex.enable = true; + ### RECURRING-DONATIONS + # Enable this module to send recurring donations. Only enable this if + # clightning is enabled. + # services.recurring-donations.enable = true; + # Specify the receivers of the donations. By default donations are every + # Monday at a randomized time. + # services.recurring-donations.tallycoin = { + # "djbooth007" = 1000; + # "hillebrandmax" = 1000; + # }; + # FIXME: Define your hostname. networking.hostName = "nix-bitcoin"; time.timeZone = "UTC"; diff --git a/modules/default.nix b/modules/default.nix index ad57e84..d476ef4 100644 --- a/modules/default.nix +++ b/modules/default.nix @@ -10,4 +10,5 @@ nix-bitcoin-pkgs = ./nix-bitcoin-pkgs.nix; nix-bitcoin-webindex = ./nix-bitcoin-webindex.nix; spark-wallet = ./spark-wallet.nix; + recurring-donations = ./recurring-donations.nix } diff --git a/modules/nix-bitcoin.nix b/modules/nix-bitcoin.nix index 26a0aa9..8c56d90 100644 --- a/modules/nix-bitcoin.nix +++ b/modules/nix-bitcoin.nix @@ -26,6 +26,7 @@ in { ./spark-wallet.nix ./electrs.nix ./onion-chef.nix + ./recurring-donations.nix ]; options.services.nix-bitcoin = { diff --git a/modules/recurring-donations.nix b/modules/recurring-donations.nix new file mode 100644 index 0000000..7edd91b --- /dev/null +++ b/modules/recurring-donations.nix @@ -0,0 +1,108 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.recurring-donations; + recurring-donations-script = pkgs.writeScript "recurring-donations.sh" '' + LNCLI="lightning-cli --lightning-dir=${config.services.clightning.dataDir}" + pay_tallycoin() { + NAME=$1 + AMOUNT=$2 + echo Attempting to pay $AMOUNT sat to $NAME + INVOICE=$(torsocks curl -d "satoshi_amount=$AMOUNT&payment_method=ln&id=$NAME&type=profile" -X POST https://api.tallyco.in/v1/payment/request/ | jq -r '.lightning_pay_request') 2> /dev/null + if [ -z "$INVOICE" ] || [ "$INVOICE" = "null" ]; then + echo "ERROR: did not get invoice from tallycoin" + return + fi + # Decode invoice and compare amount with requested amount + DECODED_AMOUNT=$($LNCLI decodepay "$INVOICE" | jq -r '.amount_msat' | head -c 4) + if [ -z "$DECODED_AMOUNT" ] || [ "$DECODED_AMOUNT" = "null" ]; then + echo "ERROR: did not get response from clightning" + return + fi + if [ $DECODED_AMOUNT -eq $AMOUNT ]; then + echo Paying with invoice "$INVOICE" + $LNCLI pay "$INVOICE" + else + echo ERROR: requested amount and invoice amount do not match. $AMOUNT vs $DECODED_AMOUNT + return + fi + } + ${ builtins.foldl' + (x: receiver: x + + '' + pay_tallycoin ${receiver} ${toString (builtins.getAttr receiver cfg.tallycoin)} + '') + "" + (builtins.attrNames cfg.tallycoin) + } + ''; +in { + options.services.recurring-donations = { + enable = mkOption { + type = types.bool; + default = false; + description = '' + If enabled, the recurring-donations service will be installed. + ''; + }; + tallycoin = mkOption { + type = types.attrs; + default = {}; + description = '' + This option is used to specify tallycoin donation receivers using an + attribute set. For example the following setting instructs the module + to repeatedly send 1000 satoshis to djbooth007. + { + "djbooth007" = 1000; + } + ''; + }; + interval = mkOption { + type = types.string; + default = "Mon *-*-* 00:00:00"; + description = '' + Schedules the donations. Default is weekly on Mon 00:00:00. See `man + systemd.time` for further options. + ''; + }; + randomizedDelaySec = mkOption { + type = types.int; + default = 86400; + description = '' + Random delay to add to scheduled time for donation. Default is one day. + ''; + }; + }; + + config = mkIf cfg.enable { + systemd.services.recurring-donations = { + description = "Run recurring-donations"; + requires = [ "clightning.service" ]; + after = [ "clightning.service" ]; + path = [ pkgs.clightning pkgs.curl pkgs.torsocks pkgs.sudo pkgs.jq ]; + serviceConfig = { + ExecStart = "${pkgs.bash}/bin/bash ${recurring-donations-script}"; + # TODO: would be better if this was operator, but I don't get sudo + # working inside the shell script + User = "clightning"; + Type = "oneshot"; + PrivateTmp = "true"; + ProtectSystem = "full"; + NoNewPrivileges = "true"; + PrivateDevices = "true"; + }; + }; + systemd.timers.recurring-donations = { + requires = [ "clightning.service" ]; + after = [ "clightning.service" ]; + timerConfig = { + Unit = "recurring-donations.service"; + OnCalendar = cfg.interval; + RandomizedDelaySec = toString cfg.randomizedDelaySec; + }; + wantedBy = [ "multi-user.target" ]; + }; + }; +}