diff --git a/README.md b/README.md index 4e24263..f91835f 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,13 @@ If you want to use this today, the sane way would be to fork the project, review The git server for this project runs on a shared virtual server, so don't trust unsigned or unverified messages. +Service Module Notes +--- +- [bisonw](docs/bisonw.md) +- [dcrd](docs/dcrd.md) +- [dcrwallet](docs/dcrwallet.md) +- [vspd](docs/vspd.md) + Support --- No support is provided. If you have ideas for improvement, general feedback, or a PR, you can tag @stakeynet on the Decred matrix channels. diff --git a/docs/dcrwallet.md b/docs/dcrwallet.md index 43a8c6b..d50d4a3 100644 --- a/docs/dcrwallet.md +++ b/docs/dcrwallet.md @@ -66,7 +66,7 @@ If you run dcrwallet as a service, here's how to intialize a wallet as `root`. ```bash cd /var/lib/dcrwallet -export DCRWALLET_BIN=$(systemctl cat --runtime dcrwallet.service | grep ExecStart | awk '{print $1}' | cut -d= -f2) +export DCRWALLET_BIN=$(systemctl cat --runtime dcrwallet.service | grep ExecStart= | awk '{print $1}' | cut -d= -f2) doas -u dcrwallet $DCRWALLET_BIN \ --configfile=/run/secrets/rendered/dcrwallet.conf \ --appdata=/var/lib/dcrwallet \ diff --git a/docs/vspd.md b/docs/vspd.md new file mode 100644 index 0000000..d2efe9a --- /dev/null +++ b/docs/vspd.md @@ -0,0 +1,64 @@ +# vspd options + +`vspd` uses rpc credentials, so it's recommended to secure your secrets using a tool like [sops-nix](https://github.com/Mic92/sops-nix). + +## sops-nix + +Render `vspd.conf` with `sops-nix` and point the service at it. Example: + +```nix +{ config, lib, pkgs, ... }: +{ + # Define credentials as secrets + sops.secrets."vspd/adminpass" = {}; + sops.secrets."dcrwallet/rpcpass" = {}; + + # Render vspd.conf owned by the vspd service user/group + sops.templates."vspd.conf" = { + owner = config.services.vspd.user; + group = config.services.vspd.group; + mode = "0440"; + restartUnits = [ "vspd.service" ]; + content = '' + [Application Options] + network = mainnet + + # Web server + listen = 0.0.0.0:8800 + adminpass = ${config.sops.placeholder."vspd/adminpass"} + supportemail = support@example.com + vspfee = 2.0 + + # dcrd connection + dcrdhost = 127.0.0.1:9109 + dcrduser = myusername + dcrdpass = ${config.sops.placeholder."dcrwallet/rpcpass"} + dcrdcert = /var/lib/dcrd/rpc.cert + + # dcrwallet connections + # Multiple wallets are comma-separated + wallethost = 10.0.0.1:9110,10.0.0.2:9110,10.0.0.3:9110 + walletuser = wallet1user,wallet2user,wallet3user + walletpass = ${config.sops.placeholder."dcrwallet/rpcpass"},${config.sops.placeholder."dcrwallet/rpcpass"},${config.sops.placeholder."dcrwallet/rpcpass"} + walletcert = /var/lib/vspd/wallet1.cert,/var/lib/vspd/wallet2.cert,/var/lib/vspd/wallet3.cert + ''; + }; + + # Ensure vspd only starts when the config exists + systemd.services.vspd.unitConfig.ConditionPathExists = + config.sops.templates."vspd.conf".path; + + # Point the module to the rendered config + services.vspd = { + enable = true; + configFile = config.sops.templates."vspd.conf".path; + }; +} +``` + +## Notes + +- `vspd` expects its configuration file to be named `vspd.conf` and located in its home directory. The NixOS module handles this by symlinking the file provided in `configFile` to `/var/lib/vspd/vspd.conf` on startup. +- `vspd` requires access to the `rpc.cert` files for both `dcrd` and all voting `dcrwallet` instances. Ensure permissions are set correctly so the `vspd` user can read them. +- `vspd` periodically writes a backup of its database to `{homedir}/data/{network}/vspd.db-backup`. +Ensure this file is backed up regularly. diff --git a/flake.nix b/flake.nix index 180d87e..ab3bdaa 100644 --- a/flake.nix +++ b/flake.nix @@ -20,6 +20,7 @@ dcrctl = pkgs.callPackage ./pkgs/dcrctl.nix {}; dcrwallet = pkgs.callPackage ./pkgs/dcrwallet.nix {}; bisonw = pkgs.callPackage ./pkgs/bisonw.nix {}; + vspd = pkgs.callPackage ./pkgs/vspd.nix {}; }); overlays.default = final: prev: { @@ -27,17 +28,20 @@ dcrctl = final.callPackage ./pkgs/dcrctl.nix {}; dcrwallet = final.callPackage ./pkgs/dcrwallet.nix {}; bisonw = final.callPackage ./pkgs/bisonw.nix {}; + vspd = final.callPackage ./pkgs/vspd.nix {}; }; nixosModules = { dcrd = ./modules/dcrd.nix; dcrwallet = ./modules/dcrwallet.nix; bisonw = ./modules/bisonw.nix; + vspd = ./modules/vspd.nix; default = { config, lib, pkgs, ... }: { imports = [ self.nixosModules.dcrd self.nixosModules.dcrwallet self.nixosModules.bisonw + self.nixosModules.vspd ]; nixpkgs.overlays = [ self.overlays.default ]; }; diff --git a/modules/vspd.nix b/modules/vspd.nix new file mode 100644 index 0000000..3754614 --- /dev/null +++ b/modules/vspd.nix @@ -0,0 +1,83 @@ +{ config, lib, pkgs, ... }: + +let + dcrdEnabled = config.services.dcrd.enable or false; + cfg = config.services.vspd; +in { + options.services.vspd = with lib; { + enable = mkEnableOption "Voting Service Provider Daemon"; + + package = mkOption { + type = types.package; + default = pkgs.vspd; + description = "vspd package to use"; + }; + + user = mkOption { + type = types.str; + default = "vspd"; + description = "User to run vspd as"; + }; + + group = mkOption { + type = types.str; + default = cfg.user; + description = "Group to run vspd as"; + }; + + dataDir = mkOption { + type = types.path; + default = "/var/lib/vspd"; + description = "State directory for vspd"; + }; + + configFile = mkOption { + type = types.path; + description = "Path to vspd.conf"; + }; + }; + + config = lib.mkIf cfg.enable { + users.users.${cfg.user} = { + group = cfg.group; + home = cfg.dataDir; + isSystemUser = true; + description = "vspd user"; + # vspd needs read access to the dcrd RPC certificate + extraGroups = lib.optional (dcrdEnabled && config.services.dcrd.group != cfg.group) config.services.dcrd.group; + }; + + users.groups.${cfg.group} = {}; + + systemd.services.vspd = { + description = "Voting Service Provider Daemon"; + wantedBy = [ "multi-user.target" ]; + after = [ "network-online.target" ]; + wants = [ "network-online.target" ]; + + serviceConfig = { + User = cfg.user; + Group = cfg.group; + StateDirectory = "vspd"; + StateDirectoryMode = "0750"; + WorkingDirectory = cfg.dataDir; + # Link the provided config file to the expected location in homedir + ExecStartPre = "${pkgs.writeShellScript "vspd-pre-start" '' + set -e + ln -sf ${cfg.configFile} ${cfg.dataDir}/vspd.conf + mkdir -p ${cfg.dataDir}/internal/webapi + if [ ! -e "${cfg.dataDir}/internal/webapi/public" ]; then + ln -sfn "${cfg.package}/share/vspd/internal/webapi/public" "${cfg.dataDir}/internal/webapi/public" + fi + if [ ! -e "${cfg.dataDir}/internal/webapi/templates" ]; then + ln -sfn "${cfg.package}/share/vspd/internal/webapi/templates" "${cfg.dataDir}/internal/webapi/templates" + fi + ''}"; + ExecStart = "${lib.getExe cfg.package} --homedir=${cfg.dataDir}"; + Restart = "on-failure"; + RestartSec = "10s"; + }; + }; + }; +} + diff --git a/pkgs/vspd.nix b/pkgs/vspd.nix new file mode 100644 index 0000000..6c9d97a --- /dev/null +++ b/pkgs/vspd.nix @@ -0,0 +1,38 @@ +{ + lib, + buildGoModule, + fetchFromGitHub, +}: + +buildGoModule (finalAttrs: { + pname = "vspd"; + version = "1.4.0"; + + src = fetchFromGitHub { + owner = "decred"; + repo = "vspd"; + rev = "release-v${finalAttrs.version}"; + hash = "sha256-V5vLJs82mv7uKjx9V7jx8WqqgC+YSf5XrFMKtBEbke4="; + }; + + vendorHash = "sha256-c9BUiCOTTRpsJoJ1BteFt9sOOx98eJDOsBV2jRWqx0Y="; + + subPackages = [ + "cmd/vspd" + "cmd/vspadmin" + ]; + + postInstall = '' + mkdir -p $out/share/vspd/internal/webapi + cp -r internal/webapi/public $out/share/vspd/internal/webapi/ + cp -r internal/webapi/templates $out/share/vspd/internal/webapi/ + ''; + + meta = { + homepage = "https://github.com/decred/vspd"; + description = "Voting Service Provider Daemon"; + license = with lib.licenses; [ isc ]; + mainProgram = "vspd"; + }; +}) +