{
  description = "Skynet LDAP backend";

  inputs = {
    nixpkgs.url = "nixpkgs/nixos-23.05";
    naersk.url  = "github:nix-community/naersk";
    utils.url   = "github:numtide/flake-utils";
  };

  outputs = { self, nixpkgs, utils, naersk }: utils.lib.eachDefaultSystem (system: 
  let
    pkgs            = nixpkgs.legacyPackages."${system}";
    naersk-lib      = naersk.lib."${system}";
    package_name    = "skynet_ldap_backend";
    package_update  = "update_groups";
    desc            = "Skynet LDAP backend";
  in rec {

    # `nix build`
    packages."${package_name}" = naersk-lib.buildPackage {
      pname = "${package_name}";
      root = ./.;
      
      buildInputs = [
        pkgs.openssl
        pkgs.pkg-config
      ];
    };

    defaultPackage = packages."${package_name}";

    # `nix run`
    apps."${package_name}" = utils.lib.mkApp {
      drv = packages."${package_name}";
    };

    defaultApp = apps."${package_name}";

    # `nix develop`
    devShell = pkgs.mkShell {
      nativeBuildInputs = with pkgs; [ rustc cargo pkg-config openssl];
    };
    
    nixosModule = { lib, pkgs, config, ... }: 
      with lib; 
      let
        cfg = config.services."${package_name}";
        # secret options are in the env file loaded separately
        environment_config = {
          # basic server stuff
          HOME            = cfg.home;
          DATABASE        = "database.db";
          HOST_PORT       = cfg.host_port;
          SSH_ROOT        = "skynet_old";
          
          # special categories of users
          USERS_ADMIN       = lib.strings.concatStringsSep "," cfg.users.admin;
          USERS_COMMITTEE   = lib.strings.concatStringsSep "," cfg.users.committee;
          USERS_TRAINEE     = lib.strings.concatStringsSep "," cfg.users.trainee;
          USERS_LIFETIME    = lib.strings.concatStringsSep "," cfg.users.lifetime;
          USERS_BANNED      = lib.strings.concatStringsSep "," cfg.users.banned;
          USERS_RESTRICTED  = lib.strings.concatStringsSep "," cfg.users.restricted;
        };
        
        service_name = script: lib.strings.sanitizeDerivationName("${cfg.user}@${script}");
        
        # oneshot scripts to run
        serviceGenerator = mapAttrs' (script: time: nameValuePair (service_name script) {
          description = "Service for ${desc} ${script}";
          wantedBy    = [ ];
          after       = [ "network-online.target" ];
          environment = environment_config;
          
          serviceConfig = {
            Type            = "oneshot";
            User            = "${cfg.user}";
            Group           = "${cfg.user}";
            ExecStart       = "${self.defaultPackage."${system}"}/bin/${script}";
            EnvironmentFile = [
              "${cfg.env.ldap}"
              "${cfg.env.discord}"
              "${cfg.env.mail}"
              "${cfg.env.wolves}"
            ];
          };
        });
        
        # each timer will run the above service
        timerGenerator = mapAttrs' (script: time: nameValuePair (service_name script) {
          description = "Timer for ${desc} ${script}";
          
          wantedBy    = [ "timers.target" ];
          partOf      = [ "${service_name script}.service" ];
          timerConfig = {
            OnCalendar  = time;
            Unit        = "${service_name script}.service";
            Persistent  = true;
          };
        });
        
        # modify these
        scripts = {
          # every 15 min
          "update_data"   = "*:0,15,30,45";  
          #"new_users"     = "*:5,20,35,50";      
          # groups are updated every 8 hours
          "update_groups" = "00,08,16:00:00";
        };

      in { 
        options.services."${package_name}" = {
          enable = mkEnableOption "enable ${package_name}";
          
          # keep really secret stuff in this
          env = {
            ldap = mkOption rec {
              type        = types.str;
              description = "Auth for the LDAP, has LDAP_HOST, LDAP_ADMIN, LDAP_ADMIN_PW";
            };
            discord = mkOption rec {
              type        = types.str;
              description = "Auth for the discord bot, has LDAP_DISCORD_AUTH";
            };
            mail = mkOption rec {
              type        = types.str;
              description = "Mail details, has EMAIL_SMTP, EMAIL_USER, EMAIL_PASS";
            };
            wolves = mkOption rec {
              type        = types.str;
              description = "Mail details, has WOLVES_URL, WOLVES_KEY";
            };
          };
          
          users = {
            admin = mkOption rec {
              type        = types.listOf types.str;
              default     = [];
              description = "array of admins";
            };
            committee = mkOption rec {
              type        = types.listOf types.str;
              default     = [];
              description = "array of committee members";
            };
            trainee = mkOption rec {
              type        = types.listOf types.str;
              default     = [];
              description = "array of trainee admins";
            };
            lifetime = mkOption rec {
              type        = types.listOf types.str;
              default     = [];
              description = "array of lifetime users";
            };
            banned = mkOption rec {
              type        = types.listOf types.str;
              default     = [];
              description = "array of banned users";
            };
            restricted = mkOption rec {
              type        = types.listOf types.str;
              default     = [];
              description = "array of restricted user accounts";
            };
          };
          
          host_port = mkOption rec {
            type        = types.str;
            default     = "127.0.0.1:8087";
            description = "host/port for teh server tro run on";
          };
          
          # specific for teh program running
          user = mkOption rec {
            type        = types.str;
            default     = "${package_name}";
            description = "The user to run the service";
          };
           
          home = mkOption rec {
            type        = types.str;
            default     = "/etc/${cfg.prefix}${package_name}";
            description = "The home for the user";
          };
          
          prefix = mkOption rec {
            type = types.str;
            default = "skynet_";
            example = default;
            description = "The prefix used to name service/folders";
          };
            
        };

        config = mkIf cfg.enable {
        
          users.groups."${cfg.user}" = { };
          
          users.users."${cfg.user}" = {
            createHome    = true;
            isSystemUser  = true;
            home          = "${cfg.home}";
            group         = "${cfg.user}";
          };
          
          systemd.services = {
            # main service
            "${cfg.user}" = {
              description = desc;
              wantedBy    = [ "multi-user.target" ];
              after       = [ "network-online.target" ];
              wants       = [ ];
              environment = environment_config;
              
              serviceConfig = {
                # because we are storing data we need a home for it
                User            = "${cfg.user}";
                Group           = "${cfg.user}";
                Restart         = "always";
                ExecStart       = "${self.defaultPackage."${system}"}/bin/${package_name}";
                # multiple files
                EnvironmentFile = [
                  "${cfg.env.ldap}"
                  "${cfg.env.discord}"
                  "${cfg.env.mail}"
                  "${cfg.env.wolves}"
                ];
              };
              restartTriggers = [
                "${cfg.env.ldap}"
                "${cfg.env.discord}"
                "${cfg.env.mail}"
                "${cfg.env.wolves}"
              ];
            };
          } // serviceGenerator scripts;
          
          # timers to run the above services
          systemd.timers = timerGenerator scripts;
          
        };
        
      };
    
  });
}