From 96f18a0163b7b880fda1d0e73acb92d6ca03d661 Mon Sep 17 00:00:00 2001
From: Julien Moutinho <julm@autogeree.net>
Date: Mon, 13 Jan 2020 05:17:41 +0000
Subject: [PATCH] nix: improve shell.nix's modules system

---
 shell.nix                                     |  84 +++---
 shell/configuration.nix                       |   8 -
 shell/configuration/gnupg.nix                 |  24 --
 shell/gnupg/keys.nix                          |  15 ++
 .../development/libraries/nix-plugins.nix     | 133 ++++-----
 shell/modules/shells/nix-shell.nix            |  21 ++
 shell/modules/tools/networking/openssh.nix    |  26 ++
 .../modules/tools/package-management/nix.nix  |  37 +++
 shell/modules/tools/security/gnupg.nix        | 254 +++++++++---------
 9 files changed, 311 insertions(+), 291 deletions(-)
 delete mode 100644 shell/configuration.nix
 delete mode 100644 shell/configuration/gnupg.nix
 create mode 100644 shell/gnupg/keys.nix
 create mode 100644 shell/modules/shells/nix-shell.nix
 create mode 100644 shell/modules/tools/networking/openssh.nix
 create mode 100644 shell/modules/tools/package-management/nix.nix

diff --git a/shell.nix b/shell.nix
index 0830b72..8d63df7 100644
--- a/shell.nix
+++ b/shell.nix
@@ -4,51 +4,47 @@ let
     config   = {}; # Make the config pure, ignoring user's config.
     overlays = import ./overlays.nix;
   };
-  # Using modules enables to separate specific configurations in shell/configuration.nix
-  # from reusable code in shell/modules.nix
+  nixos = pkgs.nixos {};
+
+  # Configuration of shell/modules/
+  configuration = {config, ...}: {
+    imports = [
+    ];
+    nix-plugins = {
+      enable = true;
+    };
+    gnupg = {
+      enable = true;
+      gnupgHome = toString ../sec/gnupg;
+      keys = import shell/gnupg/keys.nix;
+    };
+    openssh = {
+      enable = true;
+      sshConf = ''
+      '';
+    };
+  };
+
+  # Using modules enables to separate specific configurations
+  # from reusable code in shell/modules.nix and shell/modules/
   # which may find its way in another git repository one day.
   modules =
     (import shell/modules.nix {
       inherit pkgs;
       inherit (pkgs) lib;
-      modules = [ ( import shell/configuration.nix ) ];
+      modules = [ configuration ];
     }).config;
-  /*
-  sourcephile-nix-build =
-    pkgs.stdenv.mkDerivation {
-      name = "sourcephile-nix-build";
-      preferLocalBuild = true;
-      allowSubstitutes = false;
-      inherit (pkgs) coreutils;
-      builder = pkgs.writeText "builder.sh" modules.init.builder;
-    };
-  */
-  sourcephile-nix-build =
-    pkgs.buildEnv {
-      name        = "sourcephile-nix-build";
-      pathsToLink = [ "/bin" ];
-      paths       = with modules; [
-        gnupg.init
-        #gnupg.gpg-fingerprint
-        #nix-plugins.nix-with-extra-builtins
-      ];
-    };
-  nixos = pkgs.nixos {};
-  nixos-generate-config = nixos.nixos-generate-config;
-  nixos-install         = nixos.nixos-install;
-  nixos-enter           = nixos.nixos-enter;
 in
 pkgs.stdenv.mkDerivation {
   name = "sourcephile-nix";
   src = null;
   #preferLocalBuild = true;
   #allowSubstitutes = false;
-  buildInputs = [
-    sourcephile-nix-build
+  buildInputs = modules.nix-shell.buildInputs ++ [
     nixpkgs
-    nixos-generate-config
-    nixos-install
-    nixos-enter
+    nixos.nixos-generate-config
+    nixos.nixos-install
+    nixos.nixos-enter
     #pkgs.binutils
     pkgs.coreutils
     pkgs.cryptsetup
@@ -97,8 +93,10 @@ pkgs.stdenv.mkDerivation {
   shellHook = ''
     echo >&2 "nix: running shellHook"
 
+    ${modules.nix-shell.shellHook}
+
     # nix
-    export NIX_PATH="nixpkgs=${nixpkgs}:nixpkgs-sourcephile=$PWD/.lib/nixpkgs-sourcephile"
+    export NIX_PATH="nixpkgs=${nixpkgs}"
     NIX_PATH+=":nixpkgs-overlays="$PWD"/overlays"
     #NIX_PATH+=""
 
@@ -108,15 +106,6 @@ pkgs.stdenv.mkDerivation {
     PATH_FHS="$PWD"/.lib/nix/fhs-bin
     PATH_FHS_VBOX="$PWD"/.lib/fhs-vbox-bin
     export PATH="$PATH_NIXOS:$PATH_FHS_VBOX:$PATH_FHS:$PATH:$PATH_NIX"
-    ln -sfn ${sourcephile-nix-build}/bin "$PWD"/.bin
-
-    # nix.conf
-    export NIX_CONF_DIR="$PWD"/.config/nix
-    install -D /dev/stdin "$PWD"/.config/nix/nix.conf <<-EOF
-    auto-optimise-store = true
-    plugin-files        = ${pkgs.nix-plugins}/lib/nix/plugins/libnix-extra-builtins.so
-    extra-builtins-file = ${modules.nix-plugins.extra-builtins}
-    EOF
 
     # NOTE: sudo needs to be own by root with the setuid bit,
     # but this won't be the case for the sudo provided by Nix outside NixOS,
@@ -129,20 +118,9 @@ pkgs.stdenv.mkDerivation {
     export LANG=fr_FR.UTF-8
     export LC_CTYPE=fr_FR.UTF-8
 
-    # gnupg
-    export GNUPGHOME="$PWD"/../sec/gnupg
-    install -dm700 "$GNUPGHOME"
-    export GPG_TTY=$(tty)
-    gpgconf --launch gpg-agent
-    export SSH_AUTH_SOCK=$(gpgconf --list-dirs agent-ssh-socket)
-
     # password-store
     export PASSWORD_STORE_DIR="$PWD"/../sec/pass
 
-    # openssl
-    export NIX_SSL_CERT_FILE="${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt"
-    export SSL_CERT_FILE="${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt"
-
     # git
     gitdir="$PWD"/.git
     test ! -f "$gitdir" || while IFS=" :" read -r hdr gitdir; do [ "$hdr" != gitdir ] || break; done <"$gitdir"
diff --git a/shell/configuration.nix b/shell/configuration.nix
deleted file mode 100644
index d8dacfd..0000000
--- a/shell/configuration.nix
+++ /dev/null
@@ -1,8 +0,0 @@
-{config, ...}:
-{
-  imports = [
-    configuration/gnupg.nix
-  ];
-  config = {
-  };
-}
diff --git a/shell/configuration/gnupg.nix b/shell/configuration/gnupg.nix
deleted file mode 100644
index be5e798..0000000
--- a/shell/configuration/gnupg.nix
+++ /dev/null
@@ -1,24 +0,0 @@
-{config, ...}:
-{
-  config = {
-    gnupg = {
-      enable = true;
-      dir.var = toString ../../../sec/gnupg;
-      keys = {
-        "Julien Moutinho <julm@sourcephile.fr>" = {
-          uid = "Julien Moutinho <julm@sourcephile.fr>";
-          algo   = "rsa4096";
-          expire = "3y";
-          usage  = ["cert" "sign"];
-          passPath = "members/julm/gpg/password";
-          subKeys = [
-            { algo = "rsa4096"; expire = "3y"; usage = ["sign"];}
-            { algo = "rsa4096"; expire = "3y"; usage = ["encrypt"];}
-            { algo = "rsa4096"; expire = "3y"; usage = ["auth"];}
-            ];
-          backupRecipients = [""];
-        };
-      };
-    };
-  };
-}
diff --git a/shell/gnupg/keys.nix b/shell/gnupg/keys.nix
new file mode 100644
index 0000000..91c22d9
--- /dev/null
+++ b/shell/gnupg/keys.nix
@@ -0,0 +1,15 @@
+{
+  "Julien Moutinho <julm@sourcephile.fr>" = {
+    uid = "Julien Moutinho <julm@sourcephile.fr>";
+    algo   = "rsa4096";
+    expire = "3y";
+    usage  = ["cert" "sign"];
+    passPath = "members/julm/gpg/password";
+    subKeys = [
+      { algo = "rsa4096"; expire = "3y"; usage = ["sign"];}
+      { algo = "rsa4096"; expire = "3y"; usage = ["encrypt"];}
+      { algo = "rsa4096"; expire = "3y"; usage = ["auth"];}
+      ];
+    backupRecipients = [""];
+  };
+}
diff --git a/shell/modules/development/libraries/nix-plugins.nix b/shell/modules/development/libraries/nix-plugins.nix
index 3a1b182..a1a2798 100644
--- a/shell/modules/development/libraries/nix-plugins.nix
+++ b/shell/modules/development/libraries/nix-plugins.nix
@@ -1,17 +1,61 @@
 { config, lib, pkgs, ... }:
-with lib;
 let cfg = config.nix-plugins;
+    inherit (lib) types;
+
+    # Wrapper around nix to load extra-builtins.nix with nix-plugins.
+    nix-with-extra-builtins = pkgs.writeShellScriptBin "nix-with-extra-builtins" ''
+      ${pkgs.nix}/bin/nix \
+       --option plugin-files ${pkgs.nix-plugins}/lib/nix/plugins/libnix-extra-builtins.so \
+       --option extra-builtins-file ${cfg.extra-builtins} \
+       "$@"
+    '';
+
+    # Wrapper around pass to call it with exec in extra-builtins.nix.
+    # Unfortunately it can only load secrets which can be represented as a Nix string,
+    # hence without null-byte and such special characters.
+    nix-pass = pkgs.writeShellScriptBin "nix-pass" ''
+      set -e
+      f=$(mktemp)
+      trap "shred -u $f" EXIT
+      ${pkgs.pass}/bin/pass show "$1" >$f
+      nix-instantiate --eval -E "builtins.readFile $f"
+    '';
+    /*
+      nix-store --add $f
+    */
+    /*
+      set -o pipefail
+      ${pkgs.pass}/bin/pass show "$1" |
+      ${pkgs.gnused}/bin/sed \
+       -e 's:\n:\\n:g;s:\r:\\r:g;s:\t:\\t:g;s:":\\":g;1s:^:":;$s:$:":;'
+    */
+
+    # Wrapper around pass to call it with exec in extra-builtins.nix and put the output in a file.
+    # Needed for boot.initrd.network.ssh.host*Key.
+    nix-pass-to-file = pkgs.writeShellScriptBin "nix-pass-to-file" ''
+      set -e
+      set -o pipefail
+      ${pkgs.pass}/bin/pass show "$1" |
+      install -D -m 400 /dev/stdin "$2"
+      printf '%s\n' "$PWD/$2"
+    '';
+
+    # Wrapper around git to call it with exec in extra-builtins.nix.
+    nix-git = pkgs.writeShellScriptBin "nix-git" ''
+      cd "$1"; shift
+      ${pkgs.git}/bin/git "$@"
+    '';
 in
 {
   options.nix-plugins = {
     enable = lib.mkEnableOption "nix-plugins";
-    extra-builtins = mkOption {
+    extra-builtins = lib.mkOption {
       type = types.lines;
       default = ''
-        pass         = path: exec [ "${config.nix-plugins.nix-pass}/bin/nix-pass" path ];
-        pass-to-file = path: file: exec [ "${config.nix-plugins.nix-pass-to-file}/bin/nix-pass-to-file" path file ];
-        git          = dir: args: exec ([ "${config.nix-plugins.nix-git}/bin/nix-git" (builtins.toPath dir) ] ++ args);
-        git-time     = dir: path: exec [ "${config.nix-plugins.nix-git}/bin/nix-git" (builtins.toPath dir) "log" "-1" "--format=%ct" "--" path ];
+        pass         = path: exec [ "${nix-pass}/bin/nix-pass" path ];
+        pass-to-file = path: file: exec [ "${nix-pass-to-file}/bin/nix-pass-to-file" path file ];
+        git          = dir: args: exec ([ "${nix-git}/bin/nix-git" (builtins.toPath dir) ] ++ args);
+        git-time     = dir: path: exec [ "${nix-git}/bin/nix-git" (builtins.toPath dir) "log" "-1" "--format=%ct" "--" path ];
       '';
       description = ''
         Content put in extra-builtins.nix for nix-plugins.
@@ -21,75 +65,14 @@ in
         {
         '' + lines + ''
         }
-        '');
-    };
-
-    nix-with-extra-builtins = mkOption {
-      type = types.str;
-      apply = pkgs.writeShellScriptBin "nix-with-extra-builtins";
-      default = ''
-        ${pkgs.nix}/bin/nix \
-         --option plugin-files ${pkgs.nix-plugins}/lib/nix/plugins/libnix-extra-builtins.so \
-         --option extra-builtins-file ${cfg.extra-builtins} \
-         "$@"
-      '';
-      description = ''
-        Wrapper around nix to load extra-builtins.nix with nix-plugins.
-      '';
-    };
-
-    nix-pass = mkOption {
-      type = types.str;
-      apply = pkgs.writeShellScriptBin "nix-pass";
-      default = ''
-        set -e
-        f=$(mktemp)
-        trap "shred -u $f" EXIT
-        ${pkgs.pass}/bin/pass show "$1" >$f
-        nix-instantiate --eval -E "builtins.readFile $f"
-      '';
-      /*
-        nix-store --add $f
-      */
-      /*
-        set -o pipefail
-        ${pkgs.pass}/bin/pass show "$1" |
-        ${pkgs.gnused}/bin/sed \
-         -e 's:\n:\\n:g;s:\r:\\r:g;s:\t:\\t:g;s:":\\":g;1s:^:":;$s:$:":;'
-      */
-      description = ''
-        Wrapper around pass to call it with exec in extra-builtins.nix.
-        Unfortunately it can only load secrets which can be represented as a Nix string,
-        hence without null-byte and such special characters.
-      '';
-    };
-
-    nix-pass-to-file = mkOption {
-      type = types.str;
-      apply = pkgs.writeShellScriptBin "nix-pass-to-file";
-      default = ''
-        set -e
-        set -o pipefail
-        ${pkgs.pass}/bin/pass show "$1" |
-        install -D -m 400 /dev/stdin "$2"
-        printf '%s\n' "$PWD/$2"
-      '';
-      description = ''
-        Wrapper around pass to call it with exec in extra-builtins.nix and put the output in a file.
-        Needed for boot.initrd.network.ssh.host*Key.
-      '';
-    };
-
-    nix-git = mkOption {
-      type = types.str;
-      apply = pkgs.writeShellScriptBin "nix-git";
-      default = ''
-        cd "$1"; shift
-        ${pkgs.git}/bin/git "$@"
-      '';
-      description = ''
-        Wrapper around git to call it with exec in extra-builtins.nix.
-      '';
+      '');
     };
   };
+  config = lib.mkIf cfg.enable {
+    nix.enable = true;
+    nix.nixConf = ''
+      plugin-files        = ${pkgs.nix-plugins}/lib/nix/plugins/libnix-extra-builtins.so
+      extra-builtins-file = ${cfg.extra-builtins}
+    '';
+  };
 }
diff --git a/shell/modules/shells/nix-shell.nix b/shell/modules/shells/nix-shell.nix
new file mode 100644
index 0000000..5ee6860
--- /dev/null
+++ b/shell/modules/shells/nix-shell.nix
@@ -0,0 +1,21 @@
+{ pkgs, lib, config, ... }:
+let inherit (lib) types;
+in
+{
+  options = {
+    nix-shell.buildInputs = lib.mkOption {
+      type = types.listOf types.package;
+      default = [];
+      description = ''
+        To be prepended to the shell.nix's buildInputs.
+      '';
+    };
+    nix-shell.shellHook = lib.mkOption {
+      type = types.lines;
+      default = "";
+      description = ''
+        To be prepended to the shell.nix's shellHook.
+      '';
+    };
+  };
+}
diff --git a/shell/modules/tools/networking/openssh.nix b/shell/modules/tools/networking/openssh.nix
new file mode 100644
index 0000000..4322edc
--- /dev/null
+++ b/shell/modules/tools/networking/openssh.nix
@@ -0,0 +1,26 @@
+{ pkgs, lib, config, ... }:
+let cfg = config.openssh;
+    inherit (lib) types;
+in
+{
+  options.openssh = {
+    enable = lib.mkEnableOption "OpenSSH shell utilities";
+    sshConf = lib.mkOption {
+      type = types.lines;
+      apply = s: pkgs.writeText "ssh_config" s;
+      default = ''
+      '';
+      description = ''
+        OpenSSH's ssh_config content.
+      '';
+    };
+  };
+  config = lib.mkIf cfg.enable {
+    nix-shell.buildInputs =
+      let ssh = pkgs.writeShellScriptBin "ssh" ''
+          ${pkgs.openssh}/bin/ssh -F ${cfg.sshConf} "$@"
+        '';
+      in
+      [ ssh ];
+  };
+}
diff --git a/shell/modules/tools/package-management/nix.nix b/shell/modules/tools/package-management/nix.nix
new file mode 100644
index 0000000..88c0da6
--- /dev/null
+++ b/shell/modules/tools/package-management/nix.nix
@@ -0,0 +1,37 @@
+{ config, lib, pkgs, ... }:
+let cfg = config.nix;
+    inherit (lib) types;
+    /* Alternative which does not need to re-export envvars when called via sudo.
+       But this is maybe more clear to just (re-)export envvars.
+    nix = pkgs.writeShellScriptBin "nix" ''
+      NIX_CONF_DIR=${cfg.nixConf} \
+      NIX_SSL_CERT_FILE="${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt" \
+      SSL_CERT_FILE="${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt" \
+      ${pkgs.nix}/bin/nix "$@"
+    '';
+    */
+in
+{
+  options.nix = {
+    enable = lib.mkEnableOption "nix";
+    nixConf = lib.mkOption {
+      type = types.lines;
+      apply = s: pkgs.writeText "nix.conf" s;
+      default = ''
+        auto-optimise-store = true
+      '';
+      description = ''
+        Nix's nix.conf content.
+      '';
+    };
+  };
+  config = lib.mkIf cfg.enable {
+    #shell.buildInputs = [ nix ];
+    nix-shell.shellHook = ''
+      # nix
+      export NIX_CONF_DIR=${cfg.nixConf}
+      export NIX_SSL_CERT_FILE="${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt"
+      export SSL_CERT_FILE="${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt"
+    '';
+  };
+}
diff --git a/shell/modules/tools/security/gnupg.nix b/shell/modules/tools/security/gnupg.nix
index 083e704..c582ec0 100644
--- a/shell/modules/tools/security/gnupg.nix
+++ b/shell/modules/tools/security/gnupg.nix
@@ -17,16 +17,16 @@ let cfg = config.gnupg;
      }@primary:
       ''
       info "  generateKey uid=\"${uid}\""
-      if ! ${cfg.gpg-with-home}/bin/gpg-with-home --list-secret-keys -- "=${uid}" >/dev/null 2>/dev/null
+      if ! ${gpg-with-home}/bin/gpg-with-home --list-secret-keys -- "=${uid}" >/dev/null 2>/dev/null
        then
         ${pkgs.pass}/bin/pass "${passPath}" |
-        ${cfg.gpg-with-home}/bin/gpg-with-home \
+        ${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=$(${cfg.gpg-fingerprint}/bin/gpg-fingerprint -- "=${uid}" | head1)
-      caps=$(${cfg.gpg-with-home}/bin/gpg-with-home \
+      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:' |
@@ -47,7 +47,7 @@ let cfg = config.gnupg;
       if ! printf '%s\n' "$caps" | ${pkgs.gnugrep}/bin/grep -Fqx "${lettersKeyUsage usage}"
        then
         ${pkgs.pass}/bin/pass "${primary.passPath}" |
-        ${cfg.gpg-with-home}/bin/gpg-with-home \
+        ${gpg-with-home}/bin/gpg-with-home \
           --batch --pinentry-mode loopback --passphrase-fd 0 \
           --quick-add-key "$fpr" "${algo}" "${unwords usage}" "${expire}"
        fi
@@ -62,72 +62,72 @@ let cfg = config.gnupg;
       lib.optionalString (backupRecipients != [])
       ''
       info "    generateBackupKey backupRecipients=[${unwords (map (s: "\\\"${s}\\\"") backupRecipients)}]"
-      mkdir -p "${cfg.dir.var}/backup/${uid}/"
-      if ! test -s "${cfg.dir.var}/backup/${uid}/${fpr}.pubkey.asc"
+      mkdir -p "${cfg.gnupgHome}/backup/${uid}/"
+      if ! test -s "${cfg.gnupgHome}/backup/${uid}/${fpr}.pubkey.asc"
        then
-        ${cfg.gpg-with-home}/bin/gpg-with-home \
+        ${gpg-with-home}/bin/gpg-with-home \
           --batch \
-          --armor --yes --output "${cfg.dir.var}/backup/${uid}/${fpr}.pubkey.asc" \
+          --armor --yes --output "${cfg.gnupgHome}/backup/${uid}/${fpr}.pubkey.asc" \
           --export-options export-backup \
           --export "${fpr}"
        fi
       '' + (if backupRecipients == [""] then
       ''
-      if ! test -s "${cfg.dir.var}/backup/${uid}/${fpr}.revoke.asc"
+      if ! test -s "${cfg.gnupgHome}/backup/${uid}/${fpr}.revoke.asc"
        then
         ${pkgs.pass}/bin/pass "${passPath}" |
-        ${cfg.gpg-with-home}/bin/gpg-with-home \
+        ${gpg-with-home}/bin/gpg-with-home \
           --pinentry-mode loopback --passphrase-fd 0 \
-          --armor --yes --output "${cfg.dir.var}/backup/${uid}/${fpr}.revoke.asc" \
+          --armor --yes --output "${cfg.gnupgHome}/backup/${uid}/${fpr}.revoke.asc" \
           --gen-revoke "${fpr}"
        fi
-      if ! test -s "${cfg.dir.var}/backup/${uid}/${fpr}.privkey.sec"
+      if ! test -s "${cfg.gnupgHome}/backup/${uid}/${fpr}.privkey.sec"
        then
         ${pkgs.pass}/bin/pass "${passPath}" |
-        ${cfg.gpg-with-home}/bin/gpg-with-home \
+        ${gpg-with-home}/bin/gpg-with-home \
           --batch --pinentry-mode loopback --passphrase-fd 0 \
-          --armor --yes --output "${cfg.dir.var}/backup/${uid}/${fpr}.privkey.sec" \
+          --armor --yes --output "${cfg.gnupgHome}/backup/${uid}/${fpr}.privkey.sec" \
           --export-options export-backup \
           --export-secret-key "${fpr}"
        fi
-      if ! test -s "${cfg.dir.var}/backup/${uid}/${fpr}.subkeys.sec"
+      if ! test -s "${cfg.gnupgHome}/backup/${uid}/${fpr}.subkeys.sec"
        then
         ${pkgs.pass}/bin/pass "${passPath}" |
-        ${cfg.gpg-with-home}/bin/gpg-with-home \
+        ${gpg-with-home}/bin/gpg-with-home \
           --batch --pinentry-mode loopback --passphrase-fd 0 \
-          --armor --yes --output "${cfg.dir.var}/backup/${uid}/${fpr}.subkeys.sec" \
+          --armor --yes --output "${cfg.gnupgHome}/backup/${uid}/${fpr}.subkeys.sec" \
           --export-options export-backup \
           --export-secret-subkeys "${fpr}"
        fi
       '' else ''
-      if ! test -s "${cfg.dir.var}/backup/${uid}/${fpr}.revoke.asc.gpg"
+      if ! test -s "${cfg.gnupgHome}/backup/${uid}/${fpr}.revoke.asc.gpg"
        then
         ${pkgs.pass}/bin/pass "${passPath}" |
-        ${cfg.gpg-with-home}/bin/gpg-with-home \
+        ${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.dir.var}/backup/${uid}/${fpr}.revoke.asc.gpg"
+            --armor --yes --output "${cfg.gnupgHome}/backup/${uid}/${fpr}.revoke.asc.gpg"
        fi
-      if ! test -s "${cfg.dir.var}/backup/${uid}/${fpr}.privkey.sec.gpg"
+      if ! test -s "${cfg.gnupgHome}/backup/${uid}/${fpr}.privkey.sec.gpg"
        then
         ${pkgs.pass}/bin/pass "${passPath}" |
-        ${cfg.gpg-with-home}/bin/gpg-with-home \
+        ${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.dir.var}/backup/${uid}/${fpr}.privkey.sec.gpg"
+            --armor --yes --output "${cfg.gnupgHome}/backup/${uid}/${fpr}.privkey.sec.gpg"
        fi
-      if ! test -s "${cfg.dir.var}/backup/${uid}/${fpr}.subkeys.sec.gpg"
+      if ! test -s "${cfg.gnupgHome}/backup/${uid}/${fpr}.subkeys.sec.gpg"
        then
         ${pkgs.pass}/bin/pass "${passPath}" |
-        ${cfg.gpg-with-home}/bin/gpg-with-home \
+        ${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.dir.var}/backup/${uid}/${fpr}.subkeys.sec.gpg"
+            --armor --yes --output "${cfg.gnupgHome}/backup/${uid}/${fpr}.subkeys.sec.gpg"
        fi
       '');
     recipients = rs: unwords (map (r: ''--recipient "${refKey r}"'') rs);
@@ -147,7 +147,7 @@ let cfg = config.gnupg;
       # to its password file.
       ''
       # shell.gnupg.pass.passOfFingerprint
-      for fpr in $(${cfg.gpg-fingerprint}/bin/gpg-fingerprint -- "=${key.uid}")
+      for fpr in $(${gpg-fingerprint}/bin/gpg-fingerprint -- "=${key.uid}")
        do eval "pass_$fpr=\"${key.passPath}\""
        done
       '';
@@ -163,7 +163,7 @@ let cfg = config.gnupg;
         for keygrip in $keygrips
          do
           echo >&2 "gpg: forget: keygrip=$keygrip"
-          GNUPGHOME=${cfg.dir.var} \
+          GNUPGHOME=${cfg.gnupgHome} \
           ${pkgs.gnupg}/bin/gpg-connect-agent </dev/null >&2 "CLEAR_PASSPHRASE $keygrip" ||
           true
          done
@@ -178,10 +178,10 @@ let cfg = config.gnupg;
       ''
       ${unlines (map passOfFingerprint keys)}
       # presetPass
-      GNUPGHOME=${cfg.dir.var} \
+      GNUPGHOME=${cfg.gnupgHome} \
       ${pkgs.gnupg}/bin/gpgconf --launch gpg-agent
       ${head1}
-      fpr="$(${cfg.gpg-fingerprint}/bin/fingerprint -- "${uid}" | head1)"
+      fpr="$(${gpg-fingerprint}/bin/fingerprint -- "${uid}" | head1)"
       eval pass="\''${pass_$fpr}"
       if test -n "$pass"
        then
@@ -190,7 +190,7 @@ let cfg = config.gnupg;
           keygrips="$keygrips $keygrip"
           echo >&2 "gpg: preset: keygrip=$keygrip pass=$pass"
           ${pkgs.pass}/bin/pass "$pass" |
-          GNUPGHOME=${cfg.dir.var} \
+          GNUPGHOME=${cfg.gnupgHome} \
           ${pkgs.gnupg}/libexec/gpg-preset-passphrase --preset ''${XTRACE:+--verbose} $keygrip
          done
        fi
@@ -208,108 +208,83 @@ let cfg = config.gnupg;
         echo >&2 "INFO: $*"
       }
     '';
+
+    # A wrapper around gpg to set GNUPGHOME.
+    gpg-with-home = pkgs.writeScriptBin "gpg-with-home" ''
+      GNUPGHOME=${cfg.gnupgHome} \
+      exec ${pkgs.gnupg}/bin/gpg "$@"
+      '';
+
+    # 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 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 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
+    '';
+
+    # 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);
 in
 {
   options.gnupg = {
-    enable = lib.mkEnableOption "GnuPG admin utilities";
-    dir.var = lib.mkOption {
+    enable = lib.mkEnableOption "GnuPG shell utilities";
+    gnupgHome = lib.mkOption {
       type = types.path;
       default = "sec/gnupg";
       description = ''
       '';
     };
-    gpg-with-home = lib.mkOption {
-      type = types.str;
-      apply = pkgs.writeScriptBin "gpg-with-home";
-      default = ''
-        GNUPGHOME=${cfg.dir.var} \
-        exec ${pkgs.gnupg}/bin/gpg "$@"
-        '';
-      description = ''
-        A wrapper around gpg to set GNUPGHOME.
-      '';
-    };
-    gpg-fingerprint = lib.mkOption {
-      type = types.str;
-      apply = pkgs.writeScriptBin "gpg-fingerprint";
-      default = ''
-        set -eu
-        ${cfg.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
-        '';
-      description = ''
-        A wrapper around gpg to get fingerprints.
-      '';
-    };
-    gpg-keygrip = lib.mkOption {
-      type = types.str;
-      apply = pkgs.writeScriptBin "gpg-keygrip";
-      default = ''
-        set -eu
-        ${cfg.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
-        '';
-      description = ''
-        A wrapper around gpg to get keygrips.
-      '';
-    };
-    gpg-uid = lib.mkOption {
-      type = types.str;
-      apply = pkgs.writeScriptBin "gpg-uid";
-      default = ''
-        set -eu
-        ${cfg.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
-        '';
-      description = ''
-        A wrapper around gpg to get uids.
-      '';
-    };
-    init = lib.mkOption {
-      type = types.str;
-      apply = pkgs.writeShellScriptBin "init-gpg";
-      default = ''
-        set -eu
-        set -o pipefail
-        ${info}
-        info "Init GnuPG"
-        ${pkgs.coreutils}/bin/install -dm0700 -D ${cfg.dir.var}
-        ${pkgs.coreutils}/bin/ln -snf ${cfg.gpgConf}      ${cfg.dir.var}/gpg.conf
-        ${pkgs.coreutils}/bin/ln -snf ${cfg.gpgAgentConf} ${cfg.dir.var}/gpg-agent.conf
-        ${pkgs.coreutils}/bin/ln -snf ${cfg.dirmngrConf}  ${cfg.dir.var}/dirmngr.conf
-        '' +
-        generateKeys cfg.keys;
-      description = ''
-        Setup gpg.
-      '';
-    };
     keys = lib.mkOption {
       default = {};
       example =
@@ -410,14 +385,14 @@ in
       }));
     };
     dirmngrConf = lib.mkOption {
-      type = types.str;
+      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.dir.var}/dirmngr.log
+        #log-file ${cfg.gnupgHome}/dirmngr.log
         #standard-resolver
       '';
       description = ''
@@ -425,7 +400,7 @@ in
       '';
     };
     keyserverPEM = lib.mkOption {
-      type = types.str;
+      type = types.lines;
       apply = s: pkgs.writeText "keyserver.pem" s;
       default = builtins.readFile gnupg/keyserver.pem;
       description = ''
@@ -433,7 +408,7 @@ in
       '';
     };
     gpgAgentConf = lib.mkOption {
-      type = types.str;
+      type = types.lines;
       apply = s: pkgs.writeText "gpg-agent.conf" s;
       default = ''
         allow-preset-passphrase
@@ -448,7 +423,7 @@ in
       '';
     };
     gpgConf = lib.mkOption {
-      type = types.str;
+      type = types.lines;
       apply = s: pkgs.writeText "gpg.conf" s;
       default = ''
         auto-key-locate keyserver
@@ -478,4 +453,21 @@ in
       '';
     };
   };
+  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)
+    '';
+  };
 }
-- 
2.49.0