diff --git a/docs/bisonw.md b/docs/bisonw.md new file mode 100644 index 0000000..1876d07 --- /dev/null +++ b/docs/bisonw.md @@ -0,0 +1,75 @@ +# bisonw options + +`services.bisonw` wraps the Bison Wallet daemon. You can provide either a rendered configuration through `services.bisonw.settings` or point at an existing file with `services.bisonw.configFile`. + +## sops-nix + +Render `dexc.conf` with `sops-nix` and point the service at it. Example: + +```nix +{ config, lib, pkgs, ... }: +{ + # Define credentials as secrets + sops.secrets."bisonw/rpcpass" = {}; + + # Render dexc.conf owned by the bisonw service user/group + sops.templates."dexc.conf" = { + owner = config.services.bisonw.user; + group = config.services.bisonw.group; + mode = "0440"; + restartUnits = [ "bisonw.service" ]; + content = '' + [Application Options] + testnet=1 + log=info + webaddr=127.0.0.1:5758 + rpclisten=127.0.0.1:5757 + rpcuser=user + rpcpass=${config.sops.placeholder."bisonw/rpcpass"} + ''; + }; + + # Ensure bisonw only starts when the config exists + systemd.services.bisonw.unitConfig.ConditionPathExists = + config.sops.templates."dexc.conf".path; + + # Point the module to the rendered config + services.bisonw = { + enable = true; + configFile = config.sops.templates."dexc.conf".path; + }; +} +``` + +## Nix Store Configuration + +If you do not need to manage secrets, you can render settings directly into the Nix store. + +```nix +services.bisonw = { + enable = true; + settings = { + testnet = true; + log = "info"; + webaddr = "127.0.0.1:5758"; + rpclisten = [ "127.0.0.1:5757" "192.168.1.10:5757" ]; + }; +}; +``` + +## Operator Access + +Provides `bwctl` for the named user and places them in the service group so they can interact with the daemon. + +```nix +services.bisonw.operator = { + enable = true; + name = "myusername"; +}; +``` + +## Data Directory + +- `services.bisonw.dataDir` defaults to `/var/lib/bisonw`. The service owns and uses this path as `HOME` and `XDG_DATA_HOME`. +- The unit runs as the `services.bisonw.user`/`group` (default `bisonw`). + diff --git a/flake.nix b/flake.nix index 979e33c..180d87e 100644 --- a/flake.nix +++ b/flake.nix @@ -19,21 +19,25 @@ dcrd = pkgs.callPackage ./pkgs/dcrd.nix {}; dcrctl = pkgs.callPackage ./pkgs/dcrctl.nix {}; dcrwallet = pkgs.callPackage ./pkgs/dcrwallet.nix {}; + bisonw = pkgs.callPackage ./pkgs/bisonw.nix {}; }); overlays.default = final: prev: { dcrd = final.callPackage ./pkgs/dcrd.nix {}; dcrctl = final.callPackage ./pkgs/dcrctl.nix {}; dcrwallet = final.callPackage ./pkgs/dcrwallet.nix {}; + bisonw = final.callPackage ./pkgs/bisonw.nix {}; }; nixosModules = { dcrd = ./modules/dcrd.nix; dcrwallet = ./modules/dcrwallet.nix; + bisonw = ./modules/bisonw.nix; default = { config, lib, pkgs, ... }: { imports = [ self.nixosModules.dcrd self.nixosModules.dcrwallet + self.nixosModules.bisonw ]; nixpkgs.overlays = [ self.overlays.default ]; }; diff --git a/modules/bisonw.nix b/modules/bisonw.nix new file mode 100644 index 0000000..47cab5c --- /dev/null +++ b/modules/bisonw.nix @@ -0,0 +1,154 @@ +{ config, lib, pkgs, ... }: + +let + inherit (lib) + literalExpression + mkEnableOption + mkIf + mkMerge + mkOption + mkPackageOption + types; + + cfg = config.services.bisonw; + + settingsFormat = pkgs.formats.ini { + listsAsDuplicateKeys = true; + }; + + generatedConfigPath = + if cfg.configFile == null then + settingsFormat.generate "dexc.conf" { + "Application Options" = cfg.settings; + } + else + null; + + configPath = + if cfg.configFile != null then toString cfg.configFile else "${cfg.dataDir}/dexc.conf"; +in +{ + options.services.bisonw = { + enable = mkEnableOption "the Bison Wallet daemon"; + + package = mkOption { + type = types.package; + default = pkgs.bisonw; + description = "Package providing the bisonw binary"; + }; + + user = mkOption { + type = types.str; + default = "bisonw"; + description = "User account under which the Bison Wallet service runs."; + }; + + group = mkOption { + type = types.str; + default = "bisonw"; + description = "Group assigned to the Bison Wallet service."; + }; + + dataDir = mkOption { + type = types.str; + default = "/var/lib/bisonw"; + description = '' + Persistent application data directory passed to `bisonw` as `--appdata`. + The service ensures the directory exists and is owned by the configured user and group. + ''; + }; + + configFile = mkOption { + type = types.nullOr types.path; + default = null; + description = '' + Path to an existing `dexc.conf` file. When `null`, the module renders one at + `services.bisonw.dataDir + "/dexc.conf"` using `services.bisonw.settings`. + ''; + example = "/run/bisonw/dexc.conf"; + }; + + settings = mkOption { + type = types.submodule { + freeformType = types.attrsOf settingsFormat.lib.types.atom; + }; + default = { }; + description = '' + Attribute set written to the `[Application Options]` section of `dexc.conf` when `configFile` is `null`. + Keys map directly to command-line flags without the leading `--`. To emit duplicate keys, provide lists which + are rendered as repeated entries. Sensitive values placed here end up in the Nix store. + ''; + example = { + testnet = true; + log = "info"; + rpccert = "/var/lib/bisonw/rpc.cert"; + }; + }; + + operator = { + enable = mkEnableOption "Install the bisonw CLI for an operator user"; + name = mkOption { + type = types.str; + default = "operator"; + description = "Operator username to receive the bisonw CLI utility."; + }; + }; + }; + + config = mkIf cfg.enable (mkMerge [ + { + users.groups.${cfg.group} = { }; + + users.users.${cfg.user} = { + isSystemUser = true; + group = cfg.group; + description = "Bison Wallet service account"; + home = cfg.dataDir; + createHome = false; + }; + + systemd.tmpfiles.rules = [ + "d ${cfg.dataDir} 0750 ${cfg.user} ${cfg.group} -" + ]; + + systemd.services.bisonw = { + description = "Bison Wallet daemon"; + wantedBy = [ "multi-user.target" ]; + after = [ "network-online.target" ]; + wants = [ "network-online.target" ]; + environment = { + HOME = cfg.dataDir; + XDG_DATA_HOME = cfg.dataDir; + }; + serviceConfig = { + User = cfg.user; + Group = cfg.group; + WorkingDirectory = cfg.dataDir; + ExecStart = ''${lib.getExe cfg.package} --appdata=${cfg.dataDir} --config=${configPath}''; + Restart = "on-failure"; + RestartSec = 15; + UMask = "0077"; + }; + preStart = + let + copyRenderedConfig = + if cfg.configFile == null then '' + install -m 0640 -o ${cfg.user} -g ${cfg.group} ${generatedConfigPath} ${configPath} + '' else ""; + in '' + install -d -m 0750 -o ${cfg.user} -g ${cfg.group} ${cfg.dataDir} + '' + copyRenderedConfig; + restartTriggers = + lib.optional (cfg.configFile == null) generatedConfigPath + ++ lib.optional (cfg.configFile != null) (toString cfg.configFile); + }; + } + (lib.mkIf cfg.operator.enable { + users.users.${cfg.operator.name} = { + packages = [ cfg.package ]; + extraGroups = [ cfg.group ]; + }; + }) + ]); +} + diff --git a/pkgs/bisonw.nix b/pkgs/bisonw.nix new file mode 100644 index 0000000..c600a27 --- /dev/null +++ b/pkgs/bisonw.nix @@ -0,0 +1,36 @@ +{ + lib, + buildGoModule, + fetchFromGitHub, +}: + +buildGoModule (finalAttrs: { + pname = "bisonw"; + version = "1.0.4"; + + src = fetchFromGitHub { + owner = "decred"; + repo = "dcrdex"; + rev = "v${finalAttrs.version}"; + hash = "sha256-P3aIoCpQoCpc1OPMssbBezgSgMuS1lMuQxHDfPT1pGY="; + }; + + subPackages = [ + "client/cmd/bisonw" + "client/cmd/bwctl" + ]; + + # buildNpmPackage fails with: + # ERROR: The package-lock.json file does not exist! + # Luckily, upstream includes pre-built frontend files. + + vendorHash = "sha256-PsEe2UEhbeKCEmZA1SPj8QYawvtw0+vflKStFr6k5eE="; + + meta = with lib; { + description = "Bison Wallet - Multi-coin wallet with built-in DEX trading"; + homepage = "https://github.com/decred/dcrdex"; + license = licenses.blueOak100; + mainProgram = "bisonw"; + }; +}) +