1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7let
8 cfg = config.services.pcscd;
9 cfgFile = pkgs.writeText "reader.conf" (
10 builtins.concatStringsSep "\n\n" config.services.pcscd.readerConfigs
11 );
12
13 package = if config.security.polkit.enable then pkgs.pcscliteWithPolkit else pkgs.pcsclite;
14
15 pluginEnv = pkgs.buildEnv {
16 name = "pcscd-plugins";
17 paths = map (p: "${p}/pcsc/drivers") config.services.pcscd.plugins;
18 };
19
20in
21{
22 imports = [
23 (lib.mkChangedOptionModule
24 [ "services" "pcscd" "readerConfig" ]
25 [ "services" "pcscd" "readerConfigs" ]
26 (
27 config:
28 let
29 readerConfig = lib.getAttrFromPath [ "services" "pcscd" "readerConfig" ] config;
30 in
31 [ readerConfig ]
32 )
33 )
34 ];
35
36 options.services.pcscd = {
37 enable = lib.mkEnableOption "PCSC-Lite daemon, to access smart cards using SCard API (PC/SC)";
38
39 plugins = lib.mkOption {
40 type = lib.types.listOf lib.types.package;
41 defaultText = lib.literalExpression "[ pkgs.ccid ]";
42 example = lib.literalExpression "[ pkgs.pcsc-cyberjack ]";
43 description = "Plugin packages to be used for PCSC-Lite.";
44 };
45
46 readerConfigs = lib.mkOption {
47 type = lib.types.listOf lib.types.lines;
48 default = [ ];
49 example = [
50 ''
51 FRIENDLYNAME "Some serial reader"
52 DEVICENAME /dev/ttyS0
53 LIBPATH /path/to/serial_reader.so
54 CHANNELID 1
55 ''
56 ];
57 description = ''
58 Configuration for devices that aren't hotpluggable.
59
60 See {manpage}`reader.conf(5)` for valid options.
61 '';
62 };
63
64 extraArgs = lib.mkOption {
65 type = lib.types.listOf lib.types.str;
66 default = [ ];
67 description = "Extra command line arguments to be passed to the PCSC daemon.";
68 };
69 };
70
71 config = lib.mkIf config.services.pcscd.enable {
72 environment.etc."reader.conf".source = cfgFile;
73
74 environment.systemPackages = [ package ];
75 systemd.packages = [ package ];
76
77 services.pcscd.plugins = [ pkgs.ccid ];
78
79 systemd.sockets.pcscd.wantedBy = [ "sockets.target" ];
80
81 systemd.services.pcscd = {
82 environment.PCSCLITE_HP_DROPDIR = pluginEnv;
83
84 # If the cfgFile is empty and not specified (in which case the default
85 # /etc/reader.conf is assumed), pcscd will happily start going through the
86 # entire confdir (/etc in our case) looking for a config file and try to
87 # parse everything it finds. Doesn't take a lot of imagination to see how
88 # well that works. It really shouldn't do that to begin with, but to work
89 # around it, we force the path to the cfgFile.
90 #
91 # https://github.com/NixOS/nixpkgs/issues/121088
92 serviceConfig.ExecStart = [
93 ""
94 "${lib.getExe package} -f -x -c ${cfgFile} ${lib.escapeShellArgs cfg.extraArgs}"
95 ];
96 };
97 };
98}