# SPDX-FileCopyrightText: 2020 Luke Granger-Brown <depot@lukegb.com>
#
# SPDX-License-Identifier: Apache-2.0

{ pkgs, config, depot, lib, ... }:
let
  inherit (lib) mkEnableOption mkOption types mkBefore;
  mkDefault = lib.mkOverride 900;

  format = pkgs.formats.json {};

  templatePathDirectories = lib.unique (map (t: dirOf t.destination) config.my.vault.settings.template);

  # Remove empty lists at the top level because they make Vault implode.
  cleanedSettings = lib.filterAttrs (n: v: !((builtins.typeOf v) == "list" && (builtins.length v) == 0)) config.my.vault.settings;
in
{
  options.my.vault = {
    enable = mkEnableOption "vault agent";
    roleID = mkOption {
      type = types.str;
      default = config.networking.hostName;
    };
    secretIDPath = mkOption {
      type = types.str;
      default = "/var/lib/vault-agent/secret-id";
    };
    settings = mkOption {
      type = format.type;
      default = {};
    };
    bindMountStateTo = mkOption {
      type = types.nullOr types.str;
      default = null;
    };
  };

  config = {
    my.vault.enable = mkDefault true;
    my.vault.settings = {
      pid_file = mkDefault "/run/vault-agent/pid";
      vault = {
        address = mkDefault "https://vault.int.lukegb.com";
        retry.num_retries = mkDefault 1;
      };
      auto_auth.method = mkDefault [{
        type = "approle";
        config = {
          role_id_file_path = pkgs.writeText "${config.my.vault.roleID}-role-id" config.my.vault.roleID;
          secret_id_file_path = config.my.vault.secretIDPath;
          remove_secret_id_file_after_reading = false;
        };
      }];
      cache.use_auto_auth_token = mkDefault true;

      listener.unix = {
        address = mkDefault "/run/vault-agent/sock";
        tls_disable = mkDefault true;
        socket_mode = "770";
        socket_user = "vault-agent";
        socket_group = "vault-agent";
      };
    };

    users.groups.vault-agent = {};
    users.users.vault-agent = { isSystemUser = true; group = "vault-agent"; };

    systemd = lib.optionalAttrs config.my.vault.enable {
      services.vault-agent = {
        description = "Hashicorp Vault Agent";
        wants = [ "network.target" ];
        after = [ "network.target" ];
        wantedBy = [ "multi-user.target" ];
        path = with pkgs; [ glibc.bin ];
        environment.VAULT_CLIENT_TIMEOUT = "15m";
        serviceConfig = {
          RuntimeDirectory = "vault-agent";
          RuntimeDirectoryMode = "0750";
          StateDirectory = "vault-agent";
          StateDirectoryMode = "0700";

          User = "vault-agent";

          NoNewPrivileges = true;
          ProtectSystem = "strict";
          ProtectHome = "yes";

          ReadWritePaths = templatePathDirectories;

          ExecStart = "${pkgs.vault}/bin/vault agent -config=${format.generate "vault-agent.json" cleanedSettings}";
        };
      };

      mounts = lib.optional (config.my.vault.bindMountStateTo != null) {
        unitConfig.RequiresMountsFor = "${config.my.vault.bindMountStateTo} /var/lib";
        options = "bind";
        what = config.my.vault.bindMountStateTo;
        where = "/var/lib/vault-agent";
        requiredBy = [ "vault-agent.service" ];
        before = [ "vault-agent.service" ];
        wantedBy = [ "vault-agent.service" ];
      };
    };
  };
}