{ 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 ]; }; }) ]); }