# Vaultwarden is an implementation of the Bitwarden password manager backend
# service, which allows for self-hosting the synchronization of a Bitwarden
# password manager client.

{
  config,
  pkgs,
  lib,
  ...
}:

let
  vaultwardenPath = "/var/lib/bitwarden_rs"; # Default service directory
in
{

  config = lib.mkIf config.services.vaultwarden.enable {
    services.vaultwarden = {
      config = {
        DOMAIN = "https://${config.hostnames.secrets}";
        SIGNUPS_ALLOWED = false;
        SIGNUPS_VERIFY = true;
        INVITATIONS_ALLOWED = true;
        WEB_VAULT_ENABLED = true;
        ROCKET_ADDRESS = "127.0.0.1";
        ROCKET_PORT = 8222;
        WEBSOCKET_ENABLED = true;
        WEBSOCKET_ADDRESS = "0.0.0.0";
        WEBSOCKET_PORT = 3012;
        LOGIN_RATELIMIT_SECONDS = 60;
        LOGIN_RATELIMIT_MAX_BURST = 10;
        ADMIN_RATELIMIT_SECONDS = 300;
        ADMIN_RATELIMIT_MAX_BURST = 3;
      };
      environmentFile = config.secrets.vaultwarden.dest;
      dbBackend = "sqlite";
    };

    secrets.vaultwarden = {
      source = ../../../private/vaultwarden.age;
      dest = "${config.secretsDirectory}/vaultwarden";
      owner = "vaultwarden";
      group = "vaultwarden";
    };

    networking.firewall.allowedTCPPorts = [ 3012 ];

    caddy.routes = [
      {
        match = [ { host = [ config.hostnames.secrets ]; } ];
        handle = [
          {
            handler = "reverse_proxy";
            upstreams = [
              { dial = "localhost:${builtins.toString config.services.vaultwarden.config.ROCKET_PORT}"; }
            ];
            headers.request.add."X-Real-IP" = [ "{http.request.remote.host}" ];
          }
        ];
      }
    ];

    # Configure Cloudflare DNS to point to this machine
    services.cloudflare-dyndns.domains = [ config.hostnames.secrets ];

    ## Backup config

    # Open to groups, allowing for backups
    systemd.services.vaultwarden.serviceConfig.StateDirectoryMode = lib.mkForce "0770";
    systemd.tmpfiles.rules = [
      "f ${vaultwardenPath}/db.sqlite3 0660 vaultwarden vaultwarden"
      "f ${vaultwardenPath}/db.sqlite3-shm 0660 vaultwarden vaultwarden"
      "f ${vaultwardenPath}/db.sqlite3-wal 0660 vaultwarden vaultwarden"
    ];

    # Allow litestream and vaultwarden to share a sqlite database
    users.users.litestream.extraGroups = [ "vaultwarden" ];
    users.users.vaultwarden.extraGroups = [ "litestream" ];

    # Backup sqlite database with litestream
    services.litestream = {
      settings = {
        dbs = [
          {
            path = "${vaultwardenPath}/db.sqlite3";
            replicas = [
              { url = "s3://${config.backup.s3.bucket}.${config.backup.s3.endpoint}/vaultwarden"; }
            ];
          }
        ];
      };
    };

    # Don't start litestream unless vaultwarden is up
    systemd.services.litestream = {
      after = [ "vaultwarden.service" ];
      requires = [ "vaultwarden.service" ];
    };

    # Run a separate file backup on a schedule
    systemd.timers.vaultwarden-backup = {
      timerConfig = {
        OnCalendar = "*-*-* 06:00:00"; # Once per day
        Unit = "vaultwarden-backup.service";
      };
      wantedBy = [ "timers.target" ];
    };

    # Backup other Vaultwarden data to object storage
    systemd.services.vaultwarden-backup = {
      description = "Backup Vaultwarden files";
      environment.AWS_ACCESS_KEY_ID = config.backup.s3.accessKeyId;
      serviceConfig = {
        Type = "oneshot";
        User = "vaultwarden";
        Group = "backup";
        EnvironmentFile = config.secrets.backup.dest;
      };
      script = ''
        ${pkgs.awscli2}/bin/aws s3 sync \
            ${vaultwardenPath}/ \
            s3://${config.backup.s3.bucket}/vaultwarden/ \
            --endpoint-url=https://${config.backup.s3.endpoint} \
            --exclude "*db.sqlite3*" \
            --exclude ".db.sqlite3*"
      '';
    };
  };
}