## Users
## =====
##
## User- and group-related configuration.
##
## Changes made here are reflected in files such as :file:/etc/shadow,
## :file:/etc/passwd, :file:/etc/group etc. If you are familiar with
## user configuration in NixOS, please note that Liminix does not have
## the concept of "mutable users" - files in /etc/ are symlinks to
## the immutable store, so you can't e.g  change a password with
## :command:`passwd`

{
  lib,
  pkgs,
  config,
  ...
}:
let
  inherit (lib)
    concatStrings
    concatStringsSep
    mapAttrsToList
    mkOption
    types
    ;
  inherit (builtins) toString;
  inherit (pkgs.pseudofile) dir;
  passwd-file =
    let
      lines = mapAttrsToList (
        name: u:
        "${name}:${
          if u ? passwd then u.passwd else "!!"
        }:${toString u.uid}:${toString u.gid}:${u.gecos}:${u.dir}:${u.shell}\n"
      ) config.users;
    in
    concatStrings lines;
  group-file =
    let
      lines = mapAttrsToList (
        name:
        {
          gid,
          usernames ? [ ],
        }:
        "${name}:x:${toString gid}:${concatStringsSep "," usernames}\n"
      ) config.groups;
    in
    concatStrings lines;
in
{
  options = {
    users = mkOption {
      type = types.attrsOf (
        types.submodule {
          options = {
            passwd = mkOption {
              type = types.str;
              description = "encrypted password, as generated by mkpasswd -m sha512crypt";
              example = "$6$RIYL.EgWOrtoJ0/7$Z53a8sc0o6AU/kuFOGiLJKhwVavTG/deoM7JTs6luNczYSUsh4UYmhvT8sVzm.l8F/LZXhhhkC7IHQs5UGAIM/";
              default = "!!";
            };
            uid = mkOption {
              type = types.int;
            };
            gid = mkOption {
              type = types.int;
            };
            gecos = mkOption {
              type = types.str;
              default = "";
              example = "Jo Q User";
            };
            dir = mkOption {
              type = types.str;
              default = "/run";
            };
            shell = mkOption {
              type = types.str;
              default = "/bin/sh";
            };
            openssh.authorizedKeys.keys = mkOption {
              type = types.listOf types.str;
              default = [ ];
            };
          };
        }
      );
    };
    groups = mkOption {
      type = types.attrsOf (
        types.submodule {
          options = {
            gid = mkOption {
              type = types.int;
            };
            usernames = mkOption {
              type = types.listOf types.str;
              default = [ ];
            };
          };
        }
      );
    };
  };
  config =
    let
      authorized_key_files = lib.attrsets.mapAttrs (
        name: val:
        dir {
          ".ssh" = dir {
            authorized_keys = {
              inherit (val) uid gid;
              type = "f";
              mode = "0400";
              file = lib.concatStringsSep "\n" val.openssh.authorizedKeys.keys;
            };
          };
        }
      ) config.users;
    in
    {
      filesystem = dir {
        etc = dir {
          passwd = {
            file = passwd-file;
          };
          group = {
            file = group-file;
          };
        };
        home = dir authorized_key_files;
      };
    };
}