1 { config, lib, pkgs, ... }:
4 inherit (lib) mkRemovedOptionModule mkOption mkPackageOption types mkIf optionalString;
6 cfg = config.programs.gnupg;
8 agentSettingsFormat = pkgs.formats.keyValue {
9 mkKeyValue = lib.generators.mkKeyValueDefault { } " ";
14 (mkRemovedOptionModule [ "programs" "gnupg" "agent" "pinentryFlavor" ] "Use programs.gnupg.agent.pinentryPackage instead")
17 options.programs.gnupg = {
18 package = mkPackageOption pkgs "gnupg" { };
20 agent.enable = mkOption {
24 Enables GnuPG agent with socket-activation for every user session.
28 agent.enableSSHSupport = mkOption {
32 Enable SSH agent support in GnuPG agent. Also sets SSH_AUTH_SOCK
33 environment variable correctly. This will disable socket-activation
34 and thus always start a GnuPG agent per user session.
38 agent.enableExtraSocket = mkOption {
42 Enable extra socket for GnuPG agent.
46 agent.enableBrowserSocket = mkOption {
50 Enable browser socket for GnuPG agent.
54 agent.pinentryPackage = mkOption {
55 type = types.nullOr types.package;
56 example = lib.literalMD "pkgs.pinentry-gnome3";
57 default = pkgs.pinentry-curses;
58 defaultText = lib.literalMD "matching the configured desktop environment or `pkgs.pinentry-curses`";
60 Which pinentry package to use. The path to the mainProgram as defined in
61 the package's meta attriutes will be set in /etc/gnupg/gpg-agent.conf.
62 If not set by the user, it'll pick an appropriate flavor depending on the
63 system configuration (qt flavor for lxqt and plasma5, gtk2 for xfce,
64 gnome3 on all other systems with X enabled, curses otherwise).
68 agent.settings = mkOption {
69 type = agentSettingsFormat.type;
72 default-cache-ttl = 600;
75 Configuration for /etc/gnupg/gpg-agent.conf.
76 See {manpage}`gpg-agent(1)` for supported options.
80 dirmngr.enable = mkOption {
84 Enables GnuPG network certificate management daemon with socket-activation for every user session.
89 config = mkIf cfg.agent.enable {
90 programs.gnupg.agent.settings = mkIf (cfg.agent.pinentryPackage != null) {
91 pinentry-program = lib.getExe cfg.agent.pinentryPackage;
94 environment.etc."gnupg/gpg-agent.conf".source =
95 agentSettingsFormat.generate "gpg-agent.conf" cfg.agent.settings;
97 # This overrides the systemd user unit shipped with the gnupg package
98 systemd.user.services.gpg-agent = {
100 Description = "GnuPG cryptographic agent and passphrase cache";
101 Documentation = "man:gpg-agent(1)";
102 Requires = [ "sockets.target" ];
105 ExecStart = "${cfg.package}/bin/gpg-agent --supervised";
106 ExecReload = "${cfg.package}/bin/gpgconf --reload gpg-agent";
110 systemd.user.sockets.gpg-agent = {
112 Description = "GnuPG cryptographic agent and passphrase cache";
113 Documentation = "man:gpg-agent(1)";
116 ListenStream = "%t/gnupg/S.gpg-agent";
117 FileDescriptorName = "std";
119 DirectoryMode = "0700";
121 wantedBy = [ "sockets.target" ];
124 systemd.user.sockets.gpg-agent-ssh = mkIf cfg.agent.enableSSHSupport {
126 Description = "GnuPG cryptographic agent (ssh-agent emulation)";
127 Documentation = "man:gpg-agent(1) man:ssh-add(1) man:ssh-agent(1) man:ssh(1)";
130 ListenStream = "%t/gnupg/S.gpg-agent.ssh";
131 FileDescriptorName = "ssh";
132 Service = "gpg-agent.service";
134 DirectoryMode = "0700";
136 wantedBy = [ "sockets.target" ];
139 systemd.user.sockets.gpg-agent-extra = mkIf cfg.agent.enableExtraSocket {
141 Description = "GnuPG cryptographic agent and passphrase cache (restricted)";
142 Documentation = "man:gpg-agent(1)";
145 ListenStream = "%t/gnupg/S.gpg-agent.extra";
146 FileDescriptorName = "extra";
147 Service = "gpg-agent.service";
149 DirectoryMode = "0700";
151 wantedBy = [ "sockets.target" ];
154 systemd.user.sockets.gpg-agent-browser = mkIf cfg.agent.enableBrowserSocket {
156 Description = "GnuPG cryptographic agent and passphrase cache (access for web browsers)";
157 Documentation = "man:gpg-agent(1)";
160 ListenStream = "%t/gnupg/S.gpg-agent.browser";
161 FileDescriptorName = "browser";
162 Service = "gpg-agent.service";
164 DirectoryMode = "0700";
166 wantedBy = [ "sockets.target" ];
169 systemd.user.services.dirmngr = mkIf cfg.dirmngr.enable {
171 Description = "GnuPG network certificate management daemon";
172 Documentation = "man:dirmngr(8)";
173 Requires = "dirmngr.socket";
176 ExecStart = "${cfg.package}/bin/dirmngr --supervised";
177 ExecReload = "${cfg.package}/bin/gpgconf --reload dirmngr";
181 systemd.user.sockets.dirmngr = mkIf cfg.dirmngr.enable {
183 Description = "GnuPG network certificate management daemon";
184 Documentation = "man:dirmngr(8)";
187 ListenStream = "%t/gnupg/S.dirmngr";
189 DirectoryMode = "0700";
191 wantedBy = [ "sockets.target" ];
194 services.dbus.packages = mkIf (lib.elem "gnome3" (cfg.agent.pinentryPackage.flavors or [])) [ pkgs.gcr ];
196 environment.systemPackages = [ cfg.package ];
198 environment.interactiveShellInit = ''
199 # Bind gpg-agent to this TTY if gpg commands are used.
200 export GPG_TTY=$(tty)
203 programs.ssh.extraConfig = optionalString cfg.agent.enableSSHSupport ''
204 # The SSH agent protocol doesn't have support for changing TTYs; however we
205 # can simulate this with the `exec` feature of openssh (see ssh_config(5))
206 # that hooks a command to the shell currently running the ssh program.
207 Match host * exec "${pkgs.runtimeShell} -c '${cfg.package}/bin/gpg-connect-agent --quiet updatestartuptty /bye >/dev/null 2>&1'"
210 environment.extraInit = mkIf cfg.agent.enableSSHSupport ''
211 if [ -z "$SSH_AUTH_SOCK" ]; then
212 export SSH_AUTH_SOCK=$(${cfg.package}/bin/gpgconf --list-dirs agent-ssh-socket)
218 assertion = cfg.agent.enableSSHSupport -> !config.programs.ssh.startAgent;
219 message = "You can't use ssh-agent and GnuPG agent with SSH support enabled at the same time!";