postgresql: install for openconcerto1 database
[sourcephile-nix.git] / shell / modules / tools / security / gnupg.nix
index c582ec0fa98adb2261d739e0330e77ee8e32b489..4c2f3b1921efc476fb3d04cdd66044d03e4fae71 100644 (file)
 { pkgs, lib, config, ... }:
-let cfg = config.gnupg;
-    inherit (lib) types;
-    unlines = builtins.concatStringsSep "\n";
-    unwords = builtins.concatStringsSep " ";
+let
+  inherit (lib) types;
+  inherit (config) gnupg;
+  unlines = builtins.concatStringsSep "\n";
+  unwords = builtins.concatStringsSep " ";
 
-    generateKeys = keys: unlines (lib.mapAttrsToList generateKey keys);
-    generateKey =
-     uid:
-     { uid ? uid
-     , algo ? "future-default"
-     , usage ? ["default"]
-     , expire ? "-"
-     , passPath
-     , subKeys ? {}
-     , ...
-     }@primary:
-      ''
-      info "  generateKey uid=\"${uid}\""
-      if ! ${gpg-with-home}/bin/gpg-with-home --list-secret-keys -- "=${uid}" >/dev/null 2>/dev/null
-       then
-        ${pkgs.pass}/bin/pass "${passPath}" |
-        ${gpg-with-home}/bin/gpg-with-home \
-          --batch --pinentry-mode loopback --passphrase-fd 0 \
-          --quick-generate-key "${uid}" "${algo}" "${unwords usage}" "${expire}"
-       fi
-      ${head1}
-      fpr=$(${gpg-fingerprint}/bin/gpg-fingerprint -- "=${uid}" | head1)
-      caps=$(${gpg-with-home}/bin/gpg-with-home \
-              --with-colons --fixed-list-mode --with-fingerprint \
-              --list-secret-keys -- "=${uid}" |
-             ${pkgs.gnugrep}/bin/grep '^ssb:' |
-             ${pkgs.coreutils}/bin/cut -d : -f 12 || true)
-      ''
-      + unlines (map (generateSubKey primary) subKeys)
-      + generateBackupKey "$fpr" primary
-      ;
-    generateSubKey =
-     primary:
-     { expire ? primary.expire
-     , algo   ? primary.algo
-     , usage
-     , ...
-     }:
-      ''
-      info "    generateSubKey usage=[${unwords usage}]"
-      if ! printf '%s\n' "$caps" | ${pkgs.gnugrep}/bin/grep -Fqx "${lettersKeyUsage usage}"
-       then
-        ${pkgs.pass}/bin/pass "${primary.passPath}" |
-        ${gpg-with-home}/bin/gpg-with-home \
-          --batch --pinentry-mode loopback --passphrase-fd 0 \
-          --quick-add-key "$fpr" "${algo}" "${unwords usage}" "${expire}"
-       fi
-      '';
-    generateBackupKey =
-     fpr:
-     { passPath
-     , backupRecipients ? []
-     , uid
-     , ...
-     }:
-      lib.optionalString (backupRecipients != [])
-      ''
-      info "    generateBackupKey backupRecipients=[${unwords (map (s: "\\\"${s}\\\"") backupRecipients)}]"
-      mkdir -p "${cfg.gnupgHome}/backup/${uid}/"
-      if ! test -s "${cfg.gnupgHome}/backup/${uid}/${fpr}.pubkey.asc"
-       then
-        ${gpg-with-home}/bin/gpg-with-home \
-          --batch \
-          --armor --yes --output "${cfg.gnupgHome}/backup/${uid}/${fpr}.pubkey.asc" \
-          --export-options export-backup \
-          --export "${fpr}"
-       fi
-      '' + (if backupRecipients == [""] then
-      ''
-      if ! test -s "${cfg.gnupgHome}/backup/${uid}/${fpr}.revoke.asc"
-       then
-        ${pkgs.pass}/bin/pass "${passPath}" |
-        ${gpg-with-home}/bin/gpg-with-home \
-          --pinentry-mode loopback --passphrase-fd 0 \
-          --armor --yes --output "${cfg.gnupgHome}/backup/${uid}/${fpr}.revoke.asc" \
-          --gen-revoke "${fpr}"
-       fi
-      if ! test -s "${cfg.gnupgHome}/backup/${uid}/${fpr}.privkey.sec"
-       then
-        ${pkgs.pass}/bin/pass "${passPath}" |
-        ${gpg-with-home}/bin/gpg-with-home \
-          --batch --pinentry-mode loopback --passphrase-fd 0 \
-          --armor --yes --output "${cfg.gnupgHome}/backup/${uid}/${fpr}.privkey.sec" \
-          --export-options export-backup \
-          --export-secret-key "${fpr}"
-       fi
-      if ! test -s "${cfg.gnupgHome}/backup/${uid}/${fpr}.subkeys.sec"
-       then
-        ${pkgs.pass}/bin/pass "${passPath}" |
-        ${gpg-with-home}/bin/gpg-with-home \
-          --batch --pinentry-mode loopback --passphrase-fd 0 \
-          --armor --yes --output "${cfg.gnupgHome}/backup/${uid}/${fpr}.subkeys.sec" \
-          --export-options export-backup \
-          --export-secret-subkeys "${fpr}"
-       fi
-      '' else ''
-      if ! test -s "${cfg.gnupgHome}/backup/${uid}/${fpr}.revoke.asc.gpg"
-       then
-        ${pkgs.pass}/bin/pass "${passPath}" |
-        ${gpg-with-home}/bin/gpg-with-home \
-          --pinentry-mode loopback --passphrase-fd 0 \
-          --armor --gen-revoke "${fpr}" |
-        gpg --encrypt ${recipients backupRecipients} \
-            --armor --yes --output "${cfg.gnupgHome}/backup/${uid}/${fpr}.revoke.asc.gpg"
-       fi
-      if ! test -s "${cfg.gnupgHome}/backup/${uid}/${fpr}.privkey.sec.gpg"
-       then
-        ${pkgs.pass}/bin/pass "${passPath}" |
-        ${gpg-with-home}/bin/gpg-with-home \
-          --batch --pinentry-mode loopback --passphrase-fd 0 \
-          --armor --export-options export-backup \
-          --export-secret-key "${fpr}" |
-        gpg --encrypt ${recipients backupRecipients} \
-            --armor --yes --output "${cfg.gnupgHome}/backup/${uid}/${fpr}.privkey.sec.gpg"
-       fi
-      if ! test -s "${cfg.gnupgHome}/backup/${uid}/${fpr}.subkeys.sec.gpg"
-       then
-        ${pkgs.pass}/bin/pass "${passPath}" |
-        ${gpg-with-home}/bin/gpg-with-home \
-          --batch --pinentry-mode loopback --passphrase-fd 0 \
-          --armor --export-options export-backup \
-          --export-secret-subkeys "${fpr}" |
-        gpg --encrypt ${recipients backupRecipients} \
-            --armor --yes --output "${cfg.gnupgHome}/backup/${uid}/${fpr}.subkeys.sec.gpg"
-       fi
-      '');
-    recipients = rs: unwords (map (r: ''--recipient "${refKey r}"'') rs);
-    refKey = key: if builtins.typeOf key == "string" then key else "=${key.uid}";
-    signer = s: if s == null
-                then ""
-                else ''--sign --default-key "${refKey s}"'';
-    lettersKeyUsage = usage:
-      (if builtins.elem "encrypt" usage then "e" else "") +
-      (if builtins.elem "sign"    usage then "s" else "") +
-      (if builtins.elem "cert"    usage then "c" else "") +
-      (if builtins.elem "auth"    usage then "a" else "");
+  generateKeys = keys: unlines (lib.mapAttrsToList generateKey keys);
+  generateKey =
+   uid:
+   { uid ? uid
+   , algo ? "future-default"
+   , usage ? ["default"]
+   , expire ? "-"
+   , passPath
+   , subKeys ? {}
+   , ...
+   }@primary:
+    ''
+    info "generateKey uid=\"${uid}\""
+    if ! ${gpg-with-home}/bin/gpg-with-home --list-secret-keys -- "=${uid}" >/dev/null 2>/dev/null
+     then
+      ${pkgs.pass}/bin/pass "${passPath}" |
+      ${gpg-with-home}/bin/gpg-with-home \
+        --batch --pinentry-mode loopback --passphrase-fd 0 \
+        --quick-generate-key "${uid}" "${algo}" "${unwords usage}" "${expire}"
+     fi
+    ${head1}
+    fpr=$(${gpg-fingerprint}/bin/gpg-fingerprint -- "=${uid}" | head1)
+    caps=$(${gpg-with-home}/bin/gpg-with-home \
+            --with-colons --fixed-list-mode --with-fingerprint \
+            --list-secret-keys -- "=${uid}" |
+           ${pkgs.gnugrep}/bin/grep '^ssb:' |
+           ${pkgs.coreutils}/bin/cut -d : -f 12 || true)
+    ''
+    + unlines (map (generateSubKey primary) subKeys)
+    + generateBackupKey "$fpr" primary
+    ;
+  generateSubKey =
+   primary:
+   { expire ? primary.expire
+   , algo   ? primary.algo
+   , usage
+   , ...
+   }:
+    ''
+    info "  generateSubKey usage=[${unwords usage}]"
+    if ! printf '%s\n' "$caps" | ${pkgs.gnugrep}/bin/grep -Fqx "${lettersKeyUsage usage}"
+     then
+      ${pkgs.pass}/bin/pass "${primary.passPath}" |
+      ${gpg-with-home}/bin/gpg-with-home \
+        --batch --pinentry-mode loopback --passphrase-fd 0 \
+        --quick-add-key "$fpr" "${algo}" "${unwords usage}" "${expire}"
+     fi
+    '';
+  generateBackupKey =
+   fpr:
+   { passPath
+   , backupRecipients ? []
+   , uid
+   , ...
+   }:
+    lib.optionalString (backupRecipients != [])
+    ''
+    info "  generateBackupKey backupRecipients=[${unwords (map (s: "\\\"${s}\\\"") backupRecipients)}]"
+    mkdir -p "${gnupg.gnupgHome}/backup/${uid}/"
+    if ! test -s "${gnupg.gnupgHome}/backup/${uid}/${fpr}.pubkey.asc"
+     then
+      ${gpg-with-home}/bin/gpg-with-home \
+        --batch \
+        --armor --yes --output "${gnupg.gnupgHome}/backup/${uid}/${fpr}.pubkey.asc" \
+        --export-options export-backup \
+        --export "${fpr}"
+     fi
+    '' + (if backupRecipients == [""] then
+    ''
+    if ! test -s "${gnupg.gnupgHome}/backup/${uid}/${fpr}.revoke.asc"
+     then
+      ${pkgs.pass}/bin/pass "${passPath}" |
+      ${gpg-with-home}/bin/gpg-with-home \
+        --pinentry-mode loopback --passphrase-fd 0 \
+        --armor --yes --output "${gnupg.gnupgHome}/backup/${uid}/${fpr}.revoke.asc" \
+        --gen-revoke "${fpr}"
+     fi
+    if ! test -s "${gnupg.gnupgHome}/backup/${uid}/${fpr}.privkey.sec"
+     then
+      ${pkgs.pass}/bin/pass "${passPath}" |
+      ${gpg-with-home}/bin/gpg-with-home \
+        --batch --pinentry-mode loopback --passphrase-fd 0 \
+        --armor --yes --output "${gnupg.gnupgHome}/backup/${uid}/${fpr}.privkey.sec" \
+        --export-options export-backup \
+        --export-secret-key "${fpr}"
+     fi
+    if ! test -s "${gnupg.gnupgHome}/backup/${uid}/${fpr}.subkeys.sec"
+     then
+      ${pkgs.pass}/bin/pass "${passPath}" |
+      ${gpg-with-home}/bin/gpg-with-home \
+        --batch --pinentry-mode loopback --passphrase-fd 0 \
+        --armor --yes --output "${gnupg.gnupgHome}/backup/${uid}/${fpr}.subkeys.sec" \
+        --export-options export-backup \
+        --export-secret-subkeys "${fpr}"
+     fi
+    '' else ''
+    if ! test -s "${gnupg.gnupgHome}/backup/${uid}/${fpr}.revoke.asc.gpg"
+     then
+      ${pkgs.pass}/bin/pass "${passPath}" |
+      ${gpg-with-home}/bin/gpg-with-home \
+        --pinentry-mode loopback --passphrase-fd 0 \
+        --armor --gen-revoke "${fpr}" |
+      gpg --encrypt ${recipients backupRecipients} \
+          --armor --yes --output "${gnupg.gnupgHome}/backup/${uid}/${fpr}.revoke.asc.gpg"
+     fi
+    if ! test -s "${gnupg.gnupgHome}/backup/${uid}/${fpr}.privkey.sec.gpg"
+     then
+      ${pkgs.pass}/bin/pass "${passPath}" |
+      ${gpg-with-home}/bin/gpg-with-home \
+        --batch --pinentry-mode loopback --passphrase-fd 0 \
+        --armor --export-options export-backup \
+        --export-secret-key "${fpr}" |
+      gpg --encrypt ${recipients backupRecipients} \
+          --armor --yes --output "${gnupg.gnupgHome}/backup/${uid}/${fpr}.privkey.sec.gpg"
+     fi
+    if ! test -s "${gnupg.gnupgHome}/backup/${uid}/${fpr}.subkeys.sec.gpg"
+     then
+      ${pkgs.pass}/bin/pass "${passPath}" |
+      ${gpg-with-home}/bin/gpg-with-home \
+        --batch --pinentry-mode loopback --passphrase-fd 0 \
+        --armor --export-options export-backup \
+        --export-secret-subkeys "${fpr}" |
+      gpg --encrypt ${recipients backupRecipients} \
+          --armor --yes --output "${gnupg.gnupgHome}/backup/${uid}/${fpr}.subkeys.sec.gpg"
+     fi
+    '');
+  recipients = rs: unwords (map (r: ''--recipient "${refKey r}"'') rs);
+  refKey = key: if builtins.typeOf key == "string" then key else "=${key.uid}";
+  signer = s: if s == null
+              then ""
+              else ''--sign --default-key "${refKey s}"'';
+  lettersKeyUsage = usage:
+    (if builtins.elem "encrypt" usage then "e" else "") +
+    (if builtins.elem "sign"    usage then "s" else "") +
+    (if builtins.elem "cert"    usage then "c" else "") +
+    (if builtins.elem "auth"    usage then "a" else "");
 
-    passOfFingerprint = key:
-      # Return shell code
-      # which fills a map from the fingerprints of the given key
-      # to its password file.
-      ''
-      # shell.gnupg.pass.passOfFingerprint
-      for fpr in $(${gpg-fingerprint}/bin/gpg-fingerprint -- "=${key.uid}")
-       do eval "pass_$fpr=\"${key.passPath}\""
+  passOfFingerprint = key:
+    # Return shell code
+    # which fills a map from the fingerprints of the given key
+    # to its password file.
+    ''
+    # shell.gnupg.pass.passOfFingerprint
+    for fpr in $(${gpg-fingerprint}/bin/gpg-fingerprint -- "=${key.uid}")
+     do eval "pass_$fpr=\"${key.passPath}\""
+     done
+    '';
+  forgetPass =
+    # Return shell code
+    # which installs an exit and keyboard interruption (^C) trap
+    # removing any pass from gpg-agent
+    # whose keygrip is registered in $keygrips.
+    ''
+    # forgetPass
+    keygrips=
+    forgetPass () {
+      for keygrip in $keygrips
+       do
+        echo >&2 "gpg: forget: keygrip=$keygrip"
+        GNUPGHOME=${gnupg.gnupgHome} \
+        ${pkgs.gnupg}/bin/gpg-connect-agent </dev/null >&2 "CLEAR_PASSPHRASE $keygrip" ||
+        true
        done
-      '';
-    forgetPass =
-      # Return shell code
-      # which installs an exit and keyboard interruption (^C) trap
-      # removing any pass from gpg-agent
-      # whose keygrip is registered in $keygrips.
-      ''
-      # forgetPass
       keygrips=
-      forgetPass () {
-        for keygrip in $keygrips
-         do
-          echo >&2 "gpg: forget: keygrip=$keygrip"
-          GNUPGHOME=${cfg.gnupgHome} \
-          ${pkgs.gnupg}/bin/gpg-connect-agent </dev/null >&2 "CLEAR_PASSPHRASE $keygrip" ||
-          true
-         done
-        keygrips=
-       }
-      trap 'forgetPass' EXIT INT
-      '';
-    presetPass = keys: uid:
-      # Return shell code
-      # which preset the pass of given uid into gpg-agent,
-      # using keys to find where the pass is stored.
-      ''
-      ${unlines (map passOfFingerprint keys)}
-      # presetPass
-      GNUPGHOME=${cfg.gnupgHome} \
-      ${pkgs.gnupg}/bin/gpgconf --launch gpg-agent
-      ${head1}
-      fpr="$(${gpg-fingerprint}/bin/fingerprint -- "${uid}" | head1)"
-      eval pass="\''${pass_$fpr}"
-      if test -n "$pass"
-       then
-        for keygrip in $(${cfg.gpg-keygrip}/bin/gpg-keygrip -- "$fpr")
-         do
-          keygrips="$keygrips $keygrip"
-          echo >&2 "gpg: preset: keygrip=$keygrip pass=$pass"
-          ${pkgs.pass}/bin/pass "$pass" |
-          GNUPGHOME=${cfg.gnupgHome} \
-          ${pkgs.gnupg}/libexec/gpg-preset-passphrase --preset ''${XTRACE:+--verbose} $keygrip
-         done
-       fi
-      '';
-
-    head1 = ''
-      head1(){
-        IFS= read -r line
-        cat >/dev/null # NOTE: consuming all the input avoids useless triggering of pipefail
-        printf %s "$line"
-      }
+     }
+    trap 'forgetPass' EXIT INT
     '';
-    info = ''
-      info(){
-        echo >&2 "INFO: $*"
-      }
+  presetPass = keys: uid:
+    # Return shell code
+    # which preset the pass of given uid into gpg-agent,
+    # using keys to find where the pass is stored.
+    ''
+    ${unlines (map passOfFingerprint keys)}
+    # presetPass
+    GNUPGHOME=${gnupg.gnupgHome} \
+    ${pkgs.gnupg}/bin/gpgconf --launch gpg-agent
+    ${head1}
+    fpr="$(${gpg-fingerprint}/bin/fingerprint -- "${uid}" | head1)"
+    eval pass="\''${pass_$fpr}"
+    if test -n "$pass"
+     then
+      for keygrip in $(${gnupg.gpg-keygrip}/bin/gpg-keygrip -- "$fpr")
+       do
+        keygrips="$keygrips $keygrip"
+        echo >&2 "gpg: preset: keygrip=$keygrip pass=$pass"
+        ${pkgs.pass}/bin/pass "$pass" |
+        GNUPGHOME=${gnupg.gnupgHome} \
+        ${pkgs.gnupg}/libexec/gpg-preset-passphrase --preset ''${XTRACE:+--verbose} $keygrip
+       done
+     fi
     '';
 
-    # A wrapper around gpg to set GNUPGHOME.
-    gpg-with-home = pkgs.writeScriptBin "gpg-with-home" ''
-      GNUPGHOME=${cfg.gnupgHome} \
-      exec ${pkgs.gnupg}/bin/gpg "$@"
-      '';
+  # Initialize the keyring according to gnupg.keys.
+  gpg-init = pkgs.writeShellScriptBin "gpg-init" (''
+    set -eu
+    set -o pipefail
+    ${info}
+    '' +
+    generateKeys gnupg.keys
+  );
 
-    # A wrapper around gpg to get fingerprints.
-    gpg-fingerprint = pkgs.writeScriptBin "gpg-fingerprint" ''
-      set -eu
-      ${gpg-with-home}/bin/gpg-with-home \
-       --with-colons --fixed-list-mode --with-fingerprint --with-subkey-fingerprint \
-       --list-public-keys "$@" |
-      while IFS=: read -r t x x x key x x x x uid x
-       do case $t in
-         (pub|sub|sec|ssb)
-          while IFS=: read -r t x x x x x x x x fpr x
-           do case $t in (fpr) printf '%s\n' "$fpr"; break;;
-           esac done
-          ;;
-       esac done
-      '';
+  # A wrapper around gpg to set GNUPGHOME.
+  gpg-with-home = pkgs.writeScriptBin "gpg-with-home" ''
+    GNUPGHOME=${gnupg.gnupgHome} \
+    exec ${pkgs.gnupg}/bin/gpg "$@"
+    '';
 
-    # A wrapper around gpg to get keygrips.
-    gpg-keygrip = pkgs.writeScriptBin "gpg-keygrip" ''
-      set -eu
-      ${gpg-with-home}/bin/gpg-with-home \
-       --with-colons --fixed-list-mode --with-keygrip \
-       --list-public-keys "$@" |
-      while IFS=: read -r t x x x key x x x x uid x
-       do case $t in
-         (pub|sub|sec|ssb)
-          while IFS=: read -r t x x x x x x x x grp x
-           do case $t in (grp) printf '%s\n' "$grp"; break;;
-           esac done
-          ;;
-       esac done
-      '';
+  # A wrapper around gpg to get fingerprints.
+  gpg-fingerprint = pkgs.writeScriptBin "gpg-fingerprint" ''
+    set -eu
+    ${gpg-with-home}/bin/gpg-with-home \
+     --with-colons --fixed-list-mode --with-fingerprint --with-subkey-fingerprint \
+     --list-public-keys "$@" |
+    while IFS=: read -r t x x x key x x x x uid x
+     do case $t in
+       (pub|sub|sec|ssb)
+        while IFS=: read -r t x x x x x x x x fpr x
+         do case $t in (fpr) printf '%s\n' "$fpr"; break;;
+         esac done
+        ;;
+     esac done
+    '';
 
-    # A wrapper around gpg to get uids.
-    gpg-uid = pkgs.writeScriptBin "gpg-uid" ''
-      set -eu
-      ${gpg-with-home}/bin/gpg-with-home \
-       --with-colons --fixed-list-mode \
-       --list-public-keys "$@" |
-      while IFS=: read -r t st x x x x x id x uid x
-       do case $t in
-         (uid)
-          case $st in
-           (u) printf '%s\n' "$uid";;
-           esac
-          ;;
-       esac done
+  # A wrapper around gpg to get keygrips.
+  gpg-keygrip = pkgs.writeScriptBin "gpg-keygrip" ''
+    set -eu
+    ${gpg-with-home}/bin/gpg-with-home \
+     --with-colons --fixed-list-mode --with-keygrip \
+     --list-public-keys "$@" |
+    while IFS=: read -r t x x x key x x x x uid x
+     do case $t in
+       (pub|sub|sec|ssb)
+        while IFS=: read -r t x x x x x x x x grp x
+         do case $t in (grp) printf '%s\n' "$grp"; break;;
+         esac done
+        ;;
+     esac done
     '';
 
-    # Initialize the keyring according to cfg.keys.
-    gpg-init = pkgs.writeShellScriptBin "gpg-init" (''
-      set -eu
-      set -o pipefail
-      ${info}
-      info "Init GnuPG"
-      ${pkgs.coreutils}/bin/install -dm0700 -D ${cfg.gnupgHome}
-      ${pkgs.coreutils}/bin/ln -snf ${cfg.gpgConf}      ${cfg.gnupgHome}/gpg.conf
-      ${pkgs.coreutils}/bin/ln -snf ${cfg.gpgAgentConf} ${cfg.gnupgHome}/gpg-agent.conf
-      ${pkgs.coreutils}/bin/ln -snf ${cfg.dirmngrConf}  ${cfg.gnupgHome}/dirmngr.conf
-      '' +
-      generateKeys cfg.keys);
+  # A wrapper around gpg to get uids.
+  gpg-uid = pkgs.writeScriptBin "gpg-uid" ''
+    set -eu
+    ${gpg-with-home}/bin/gpg-with-home \
+     --with-colons --fixed-list-mode \
+     --list-public-keys "$@" |
+    while IFS=: read -r t st x x x x x id x uid x
+     do case $t in
+       (uid)
+        case $st in
+         (u) printf '%s\n' "$uid";;
+         esac
+        ;;
+     esac done
+  '';
+
+  head1 = ''
+    head1(){
+      IFS= read -r line
+      cat >/dev/null # NOTE: consuming all the input avoids useless triggering of pipefail
+      printf %s "$line"
+    }
+  '';
+  info = ''
+    info(){
+      echo >&2 "gpg-init: $*"
+    }
+  '';
 in
 {
-  options.gnupg = {
-    enable = lib.mkEnableOption "GnuPG shell utilities";
-    gnupgHome = lib.mkOption {
-      type = types.path;
-      default = "sec/gnupg";
-      description = ''
-      '';
-    };
-    keys = lib.mkOption {
-      default = {};
-      example =
-        { "John Doe. <contact@example.coop>" = {
-            algo   = "rsa4096";
-            expire = "1y";
-            usage  = ["cert" "sign"];
-            passPath = "example.coop/gpg/contact";
-            subKeys = [
-              { algo = "rsa4096"; expire = "1y"; usage = ["sign"];}
-              { algo = "rsa4096"; expire = "1y"; usage = ["encrypt"];}
-              { algo = "rsa4096"; expire = "1y"; usage = ["auth"];}
-              ];
-            backupRecipients = ["@john@doe.pro"];
-          };
+options.gnupg = {
+  enable = lib.mkEnableOption "GnuPG shell utilities";
+  gnupgHome = lib.mkOption {
+    type = types.path;
+    default = "sec/gnupg";
+    description = ''
+    '';
+  };
+  keys = lib.mkOption {
+    default = {};
+    example =
+      { "John Doe. <contact@example.coop>" = {
+          algo   = "rsa4096";
+          expire = "1y";
+          usage  = ["cert" "sign"];
+          passPath = "example.coop/gpg/contact";
+          subKeys = [
+            { algo = "rsa4096"; expire = "1y"; usage = ["sign"];}
+            { algo = "rsa4096"; expire = "1y"; usage = ["encrypt"];}
+            { algo = "rsa4096"; expire = "1y"; usage = ["auth"];}
+            ];
+          backupRecipients = ["@john@doe.pro"];
+        };
+      };
+    type = types.attrsOf (types.submodule ({uid, ...}: {
+      #config.uid = lib.mkDefault uid;
+      options = {
+        uid = lib.mkOption {
+          type        = types.str;
+          example     = "John Doe <john.doe@example.coop>";
+          default     = uid;
+          description = ''
+            User ID.
+          '';
+        };
+        algo = lib.mkOption {
+          type        = types.enum [ "rsa4096" ];
+          default     = "future-default";
+          example     = "rsa4096";
+          description = ''
+            Cryptographic algorithm.
+          '';
+        };
+        expire = lib.mkOption {
+          type        = types.str;
+          default     = "1y";
+          example     = "1y";
+          description = ''
+            Expiration timeout.
+          '';
+        };
+        usage = lib.mkOption {
+          type        = with types; listOf (enum [ "cert" "sign" "encrypt" "auth" "default" ]);
+          default     = ["default"];
+          example     = ["cert" "sign" "encrypt" "auth"];
+          description = ''
+            Cryptographic usage.
+          '';
+        };
+        passPath = lib.mkOption {
+          type        = types.str;
+          example     = "gnupg/coop/example/contact@";
+          description = ''
+            Password path.
+          '';
         };
-      type = types.attrsOf (types.submodule ({uid, ...}: {
-        #config.uid = lib.mkDefault uid;
-        options = {
-          uid = lib.mkOption {
-            type        = types.str;
-            example     = "John Doe <john.doe@example.coop>";
-            default     = uid;
-            description = ''
-              User ID.
-            '';
-          };
-          algo = lib.mkOption {
-            type        = types.enum [ "rsa4096" ];
-            default     = "future-default";
-            example     = "rsa4096";
-            description = ''
-              Cryptographic algorithm.
-            '';
-          };
-          expire = lib.mkOption {
-            type        = types.str;
-            default     = "1y";
-            example     = "1y";
-            description = ''
-              Expiration timeout.
-            '';
-          };
-          usage = lib.mkOption {
-            type        = with types; listOf (enum [ "cert" "sign" "encrypt" "auth" "default" ]);
-            default     = ["default"];
-            example     = ["cert" "sign" "encrypt" "auth"];
-            description = ''
-              Cryptographic usage.
-            '';
-          };
-          passPath = lib.mkOption {
-            type        = types.str;
-            example     = "gnupg/coop/example/contact@";
-            description = ''
-              Password path.
-            '';
-          };
-          subKeys = lib.mkOption {
-            type = types.listOf (types.submodule {
-              options = {
-                algo = lib.mkOption {
-                  type        = types.enum [ "rsa4096" ];
-                  default     = "default";
-                  example     = "rsa4096";
-                  description = ''
-                    Cryptographic algorithm.
-                  '';
-                };
-                expire = lib.mkOption {
-                  type        = types.str;
-                  default     = "1y";
-                  example     = "1y";
-                  description = ''
-                    Expiration timeout.
-                  '';
-                };
-                usage = lib.mkOption {
-                  type        = with types; listOf (enum [ "sign" "encrypt" "auth" "default" ]);
-                  default     = ["default"];
-                  example     = ["sign" "encrypt" "auth"];
-                  description = ''
-                    Cryptographic usage.
-                  '';
-                };
+        subKeys = lib.mkOption {
+          type = types.listOf (types.submodule {
+            options = {
+              algo = lib.mkOption {
+                type        = types.enum [ "rsa4096" ];
+                default     = "default";
+                example     = "rsa4096";
+                description = ''
+                  Cryptographic algorithm.
+                '';
               };
-            });
-          };
-          backupRecipients = lib.mkOption {
-            type        = with types; listOf str;
-            default     = [];
-            example     = ["@john@doe.pro"];
-            description = ''
-              Backup keys used to encrypt the a backup copy of the secret keys.
-            '';
-          };
+              expire = lib.mkOption {
+                type        = types.str;
+                default     = "1y";
+                example     = "1y";
+                description = ''
+                  Expiration timeout.
+                '';
+              };
+              usage = lib.mkOption {
+                type        = with types; listOf (enum [ "sign" "encrypt" "auth" "default" ]);
+                default     = ["default"];
+                example     = ["sign" "encrypt" "auth"];
+                description = ''
+                  Cryptographic usage.
+                '';
+              };
+            };
+          });
         };
-      }));
-    };
-    dirmngrConf = lib.mkOption {
-      type = types.lines;
-      apply = s: pkgs.writeText "dirmngr.conf" s;
-      default = ''
-        allow-ocsp
-        hkp-cacert ${cfg.keyserverPEM}
-        keyserver hkps://keys.mayfirst.org
-        use-tor
-        #log-file ${cfg.gnupgHome}/dirmngr.log
-        #standard-resolver
-      '';
-      description = ''
-        GnuPG's dirmngr.conf content.
-      '';
-    };
-    keyserverPEM = lib.mkOption {
-      type = types.lines;
-      apply = s: pkgs.writeText "keyserver.pem" s;
-      default = builtins.readFile gnupg/keyserver.pem;
-      description = ''
-        dirmngr's hkp-cacert content.
-      '';
-    };
-    gpgAgentConf = lib.mkOption {
-      type = types.lines;
-      apply = s: pkgs.writeText "gpg-agent.conf" s;
-      default = ''
-        allow-preset-passphrase
-        default-cache-ttl 17200
-        default-cache-ttl-ssh 17200
-        enable-ssh-support
-        max-cache-ttl 17200
-        max-cache-ttl-ssh 17200
-      '';
-      description = ''
-        GnuPG's gpg-agent.conf content.
-      '';
-    };
-    gpgConf = lib.mkOption {
-      type = types.lines;
-      apply = s: pkgs.writeText "gpg.conf" s;
-      default = ''
-        auto-key-locate keyserver
-        cert-digest-algo SHA512
-        charset utf-8
-        default-preference-list SHA512 SHA384 SHA256 SHA224 AES256 AES192 AES CAST5 TWOFISH BZIP2 ZLIB ZIP Uncompressed
-        fixed-list-mode
-        keyid-format 0xlong
-        keyserver-options no-honor-keyserver-url
-        no-auto-key-locate
-        no-default-keyring
-        no-emit-version
-        personal-cipher-preferences AES256 AES CAST5
-        personal-digest-preferences SHA512
-        quiet
-        s2k-cipher-algo AES256
-        s2k-count 65536
-        s2k-digest-algo SHA512
-        s2k-mode 3
-        tofu-default-policy unknown
-        trust-model tofu+pgp
-        use-agent
-        utf8-strings
-      '';
-      description = ''
-        GnuPG's gpg.conf content.
+        backupRecipients = lib.mkOption {
+          type        = with types; listOf str;
+          default     = [];
+          example     = ["@john@doe.pro"];
+          description = ''
+            Backup keys used to encrypt the a backup copy of the secret keys.
+          '';
+        };
+      };
+    }));
+  };
+  dirmngrConf = lib.mkOption {
+    type = types.lines;
+    apply = s: pkgs.writeText "dirmngr.conf" s;
+    default = ''
+      allow-ocsp
+      hkp-cacert ${gnupg.keyserverPEM}
+      keyserver hkps://keys.mayfirst.org
+      #use-tor
+      #log-file ${gnupg.gnupgHome}/dirmngr.log
+      #standard-resolver
+    '';
+    description = ''
+      GnuPG's dirmngr.conf content.
+    '';
+  };
+  keyserverPEM = lib.mkOption {
+    type = types.lines;
+    apply = s: pkgs.writeText "keyserver.pem" s;
+    default = builtins.readFile gnupg/keyserver.pem;
+    description = ''
+      dirmngr's hkp-cacert content.
+    '';
+  };
+  gpgAgentConf = lib.mkOption {
+    type = types.lines;
+    apply = s: pkgs.writeText "gpg-agent.conf" s;
+    default =
+      let pinentry = pkgs.writeShellScript "pinentry" ''
+        #!${pkgs.runtimeShell}
+        # choose pinentry depending on PINENTRY_USER_DATA
+        # this *only works* with gpg2
+        # see https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=802020
+        case "''${PINENTRY_USER_DATA:-tty}" in
+        curses) exec ${pkgs.pinentry.curses}/bin/pinentry-curses "$@";;
+        #emacs)  exec ''${pkgs.pinentry.emacs}/bin/pinentry-emacs "$@";;
+        #gnome3) exec ''${pkgs.pinentry.gnome3}/bin/pinentry-gnome3 "$@";;
+        gtk-2)  exec ${pkgs.pinentry.gtk2}/bin/pinentry-gtk-2 "$@";;
+        none)   exit 1;; # do not ask for passphrase
+        #qt)     exec ''${pkgs.pinentry.qt}/bin/pinentry-qt "$@";;
+        tty)    exec ${pkgs.pinentry.tty}/bin/pinentry-tty "$@";;
+        esac
       '';
-    };
+    in ''
+      allow-loopback-pinentry
+      allow-preset-passphrase
+      default-cache-ttl 17200
+      default-cache-ttl-ssh 17200
+      enable-ssh-support
+      max-cache-ttl 17200
+      max-cache-ttl-ssh 17200
+      no-allow-external-cache
+      pinentry-program ${pinentry}
+    '';
+    description = ''
+      GnuPG's gpg-agent.conf content.
+    '';
+  };
+  gpgConf = lib.mkOption {
+    type = types.lines;
+    apply = s: pkgs.writeText "gpg.conf" (s+"\n"+gnupg.gpgExtraConf);
+    default = ''
+      auto-key-locate keyserver
+      cert-digest-algo SHA512
+      charset utf-8
+      default-preference-list SHA512 SHA384 SHA256 SHA224 AES256 AES192 AES CAST5 TWOFISH BZIP2 ZLIB ZIP Uncompressed
+      fixed-list-mode
+      keyid-format 0xlong
+      keyserver-options no-honor-keyserver-url
+      no-auto-key-locate
+      no-default-keyring
+      no-emit-version
+      personal-cipher-preferences AES256 AES CAST5
+      personal-digest-preferences SHA512
+      quiet
+      s2k-cipher-algo AES256
+      s2k-count 65536
+      s2k-digest-algo SHA512
+      s2k-mode 3
+      tofu-default-policy unknown
+      trust-model tofu+pgp
+      use-agent
+      utf8-strings
+    '';
+    description = ''
+      GnuPG's gpg.conf content.
+    '';
   };
-  config = lib.mkIf cfg.enable {
-    nix-shell.buildInputs = [
-      gpg-with-home
-      gpg-fingerprint
-      gpg-keygrip
-      gpg-uid
-      gpg-init
-    ];
-    nix-shell.shellHook = ''
-      # gnupg
-      export GNUPGHOME=${cfg.gnupgHome}
-      install -dm700 "$GNUPGHOME"
-      export GPG_TTY=$(${pkgs.coreutils}/bin/tty)
-      ${pkgs.gnupg}/bin/gpgconf --launch gpg-agent
-      export SSH_AUTH_SOCK=$(${pkgs.gnupg}/bin/gpgconf --list-dirs agent-ssh-socket)
+  gpgExtraConf = lib.mkOption {
+    type = types.lines;
+    default = "";
+    description = ''
+      GnuPG's gpg.conf extra content.
     '';
   };
+};
+config = lib.mkIf gnupg.enable {
+  nix-shell.buildInputs = [
+    gpg-with-home
+    gpg-fingerprint
+    gpg-keygrip
+    gpg-uid
+    gpg-init
+  ];
+  nix-shell.shellHook = ''
+    # gnupg
+    ${pkgs.coreutils}/bin/install -dm0700 -D ${gnupg.gnupgHome}
+    ${pkgs.coreutils}/bin/ln -snf ${gnupg.gpgConf}      ${gnupg.gnupgHome}/gpg.conf
+    ${pkgs.coreutils}/bin/ln -snf ${gnupg.gpgAgentConf} ${gnupg.gnupgHome}/gpg-agent.conf
+    ${pkgs.coreutils}/bin/ln -snf ${gnupg.dirmngrConf}  ${gnupg.gnupgHome}/dirmngr.conf
+    export GNUPGHOME=${gnupg.gnupgHome}
+    install -dm700 "$GNUPGHOME"
+    export GPG_TTY=$(${pkgs.coreutils}/bin/tty)
+    ${pkgs.gnupg}/bin/gpgconf --launch gpg-agent
+    export SSH_AUTH_SOCK=$(${pkgs.gnupg}/bin/gpgconf --list-dirs agent-ssh-socket)
+  '';
+};
 }