1 { config, lib, pkgs, ... }:
7 cfg = config.programs.gnupg;
9 agentSettingsFormat = pkgs.formats.keyValue {
10 mkKeyValue = lib.generators.mkKeyValueDefault { } " ";
13 xserverCfg = config.services.xserver;
15 defaultPinentryFlavor =
16 if xserverCfg.desktopManager.lxqt.enable
17 || xserverCfg.desktopManager.plasma5.enable
18 || xserverCfg.desktopManager.deepin.enable then
20 else if xserverCfg.desktopManager.xfce.enable then
22 else if xserverCfg.enable || config.programs.sway.enable then
31 options.programs.gnupg = {
35 defaultText = literalExpression "pkgs.gnupg";
36 description = lib.mdDoc ''
37 The gpg package that should be used.
41 agent.enable = mkOption {
44 description = lib.mdDoc ''
45 Enables GnuPG agent with socket-activation for every user session.
49 agent.enableSSHSupport = mkOption {
52 description = lib.mdDoc ''
53 Enable SSH agent support in GnuPG agent. Also sets SSH_AUTH_SOCK
54 environment variable correctly. This will disable socket-activation
55 and thus always start a GnuPG agent per user session.
59 agent.enableExtraSocket = mkOption {
62 description = lib.mdDoc ''
63 Enable extra socket for GnuPG agent.
67 agent.enableBrowserSocket = mkOption {
70 description = lib.mdDoc ''
71 Enable browser socket for GnuPG agent.
75 agent.pinentryFlavor = mkOption {
76 type = types.nullOr (types.enum pkgs.pinentry.flavors);
78 default = defaultPinentryFlavor;
79 defaultText = literalMD ''matching the configured desktop environment'';
80 description = lib.mdDoc ''
81 Which pinentry interface to use. If not null, the path to the
82 pinentry binary will be set in /etc/gnupg/gpg-agent.conf.
83 If not set at all, it'll pick an appropriate flavor depending on the
84 system configuration (qt flavor for lxqt and plasma5, gtk2 for xfce
85 4.12, gnome3 on all other systems with X enabled, ncurses otherwise).
89 agent.settings = mkOption {
90 type = agentSettingsFormat.type;
93 default-cache-ttl = 600;
95 description = lib.mdDoc ''
96 Configuration for /etc/gnupg/gpg-agent.conf.
97 See {manpage}`gpg-agent(1)` for supported options.
101 dirmngr.enable = mkOption {
104 description = lib.mdDoc ''
105 Enables GnuPG network certificate management daemon with socket-activation for every user session.
110 config = mkIf cfg.agent.enable {
111 programs.gnupg.agent.settings = {
112 pinentry-program = lib.mkIf (cfg.agent.pinentryFlavor != null)
113 "${pkgs.pinentry.${cfg.agent.pinentryFlavor}}/bin/pinentry";
116 environment.etc."gnupg/gpg-agent.conf".source =
117 agentSettingsFormat.generate "gpg-agent.conf" cfg.agent.settings;
119 # This overrides the systemd user unit shipped with the gnupg package
120 systemd.user.services.gpg-agent = {
122 Description = "GnuPG cryptographic agent and passphrase cache";
123 Documentation = "man:gpg-agent(1)";
124 Requires = [ "sockets.target" ];
127 ExecStart = "${cfg.package}/bin/gpg-agent --supervised";
128 ExecReload = "${cfg.package}/bin/gpgconf --reload gpg-agent";
132 systemd.user.sockets.gpg-agent = {
134 Description = "GnuPG cryptographic agent and passphrase cache";
135 Documentation = "man:gpg-agent(1)";
138 ListenStream = "%t/gnupg/S.gpg-agent";
139 FileDescriptorName = "std";
141 DirectoryMode = "0700";
143 wantedBy = [ "sockets.target" ];
146 systemd.user.sockets.gpg-agent-ssh = mkIf cfg.agent.enableSSHSupport {
148 Description = "GnuPG cryptographic agent (ssh-agent emulation)";
149 Documentation = "man:gpg-agent(1) man:ssh-add(1) man:ssh-agent(1) man:ssh(1)";
152 ListenStream = "%t/gnupg/S.gpg-agent.ssh";
153 FileDescriptorName = "ssh";
154 Service = "gpg-agent.service";
156 DirectoryMode = "0700";
158 wantedBy = [ "sockets.target" ];
161 systemd.user.sockets.gpg-agent-extra = mkIf cfg.agent.enableExtraSocket {
163 Description = "GnuPG cryptographic agent and passphrase cache (restricted)";
164 Documentation = "man:gpg-agent(1)";
167 ListenStream = "%t/gnupg/S.gpg-agent.extra";
168 FileDescriptorName = "extra";
169 Service = "gpg-agent.service";
171 DirectoryMode = "0700";
173 wantedBy = [ "sockets.target" ];
176 systemd.user.sockets.gpg-agent-browser = mkIf cfg.agent.enableBrowserSocket {
178 Description = "GnuPG cryptographic agent and passphrase cache (access for web browsers)";
179 Documentation = "man:gpg-agent(1)";
182 ListenStream = "%t/gnupg/S.gpg-agent.browser";
183 FileDescriptorName = "browser";
184 Service = "gpg-agent.service";
186 DirectoryMode = "0700";
188 wantedBy = [ "sockets.target" ];
191 systemd.user.services.dirmngr = mkIf cfg.dirmngr.enable {
193 Description = "GnuPG network certificate management daemon";
194 Documentation = "man:dirmngr(8)";
195 Requires = "dirmngr.socket";
198 ExecStart = "${cfg.package}/bin/dirmngr --supervised";
199 ExecReload = "${cfg.package}/bin/gpgconf --reload dirmngr";
203 systemd.user.sockets.dirmngr = mkIf cfg.dirmngr.enable {
205 Description = "GnuPG network certificate management daemon";
206 Documentation = "man:dirmngr(8)";
209 ListenStream = "%t/gnupg/S.dirmngr";
211 DirectoryMode = "0700";
213 wantedBy = [ "sockets.target" ];
216 services.dbus.packages = mkIf (cfg.agent.pinentryFlavor == "gnome3") [ pkgs.gcr ];
218 environment.systemPackages = with pkgs; [ cfg.package ];
220 environment.interactiveShellInit = ''
221 # Bind gpg-agent to this TTY if gpg commands are used.
222 export GPG_TTY=$(tty)
225 programs.ssh.extraConfig = optionalString cfg.agent.enableSSHSupport ''
226 # The SSH agent protocol doesn't have support for changing TTYs; however we
227 # can simulate this with the `exec` feature of openssh (see ssh_config(5))
228 # that hooks a command to the shell currently running the ssh program.
229 Match host * exec "${pkgs.runtimeShell} -c '${cfg.package}/bin/gpg-connect-agent --quiet updatestartuptty /bye >/dev/null 2>&1'"
232 environment.extraInit = mkIf cfg.agent.enableSSHSupport ''
233 if [ -z "$SSH_AUTH_SOCK" ]; then
234 export SSH_AUTH_SOCK=$(${cfg.package}/bin/gpgconf --list-dirs agent-ssh-socket)
239 { assertion = cfg.agent.enableSSHSupport -> !config.programs.ssh.startAgent;
240 message = "You can't use ssh-agent and GnuPG agent with SSH support enabled at the same time!";
245 # uses attributes of the linked package
246 meta.buildDocsInSandbox = false;