{
  upgradeConfig,
  lib ? (import <nixpkgs> { }).lib,
}:
with lib;
evalModules {
  modules = upgradeConfig ++ [
    {
      options = {

        enable = mkOption {
          type = types.bool;
          default = false;
          description = ''
            Whether to periodically upgrade NixOS to the latest version.
            Presumes that /etc/nixos is a git repo with a remote and
            contains a pinch file called "channels".
          '';
        };

        dates = mkOption {
          default = "04:40";
          type = types.str;
          description = ''
            Specification (in the format described by
            <citerefentry><refentrytitle>systemd.time</refentrytitle>
            <manvolnum>7</manvolnum></citerefentry>) of the time at
            which the update will occur.
          '';
        };

        repos = mkOption {
          description = ''
            Git repositories to pull before running pinch.  These are maintained
            as git checkouts at specified places in the filesystem with specified
            ownership rather than kept read-only in the nix store so that humans
            can use them both as points of intervention in the automation and to
            author and push changes back up.
          '';
          default = { };
          type = types.attrsOf (
            types.submodule {
              options = {
                url = mkOption {
                  description = "Remote git repo.";
                  type = types.str;
                };
                remoteName = mkOption {
                  description = ''Name of the git remote.  Customarily "origin".'';
                  type = types.str;
                  default = "origin";
                };
                onRemoteURLMismatch = mkOption {
                  description = ''
                    What to do if the remote URL in the git repo doesn't match the
                    URL configured here.
                  '';
                  type = types.enum [
                    "update"
                    "abort"
                  ];
                  default = "update";
                };
                onBranchMismatch = mkOption {
                  description = ''
                    What to do if a different branch is currently checked out.

                    (Changes from <literal>remoteBranch</literal> are only ever
                    merged into <literal>localBranch</literal>, so if a different
                    branch is checked out, no remote changes will be merged.)
                  '';
                  type = types.enum [
                    "continue"
                    "abort"
                  ];
                  default = "continue";
                };
                user = mkOption {
                  description = "User as which to run 'git fetch'";
                  type = types.str;
                };
                localBranch = mkOption {
                  description = "";
                  type = types.str;
                  default = "master";
                };
                remoteBranch = mkOption {
                  type = types.str;
                  default = "master";
                };
                requireSignature = mkOption {
                  type = types.bool;
                  default = true;
                  description = ''
                    Only pull when the tip of the remote ref is signed by a key
                    specifed in <literal>signingKeys</literal>.
                  '';
                };
              };
            }
          );
          example = {
            "/etc/nixos" = {
              url = "https://github.com/chkno/auto-upgrade-demo-nixos";
              user = "root";
              signingKeys = [ ./admins.asc ];
            };
            "/home/alice/.config/nixpkgs" = {
              url = "https://github.com/chkno/auto-upgrade-demo-user-nixpkgs";
              user = "alice";
              signingKeys = [
                ./admins.asc
                ./alice.asc
              ];
            };
          };
        };

        pinchFiles = mkOption {
          description = ''
            Pinch files to use for channel updates.  Typically these are inside
            <literal>repos</literal>' paths.
          '';
          type = types.listOf types.path;
          default = [ ];
          example = [ "/etc/nixos/channels" ];
        };

        userEnvironments = mkOption {
          description = ''
            User environments to update as part of an upgrade run.
          '';
          default = { };
          type = types.attrsOf (
            types.submodule {
              options = {
                package = mkOption {
                  type = types.str;
                  default = "userPackages";
                  description = ''
                    The name of the single package that will be updated.  You'll
                    want to create an 'entire user environment' package as shown in
                    https://nixos.wiki/wiki/FAQ#How_can_I_manage_software_with_nix-env_like_with_configuration.nix.3F
                  '';
                };
                otherPackagesAction = mkOption {
                  type = types.enum [
                    "remove"
                    "keep"
                  ];
                  default = "remove";
                  description = ''
                    What to do with packages other than <literal>package</literal>.

                    THIS DEFAULTS TO "remove", WHICH IS POTENTIALLY SOMEWHAT
                    DESTRUCTIVE!  This is the default because it is the recommended
                    setting -- This module recommends managing your environment
                    through your one entire-environment <literal>package</literal>.
                    This keeps your environment declarative and ensures that all
                    packages receive regular updates.
                  '';
                };
              };
            }
          );
          example = {
            alice = { };
          };
        };
      };
    }
  ];
}
