{
  config,
  lib,
  ...
}: let
  cfg = config.hive.gotify-instance;
  server-config = {
    server = {
      listenaddr = "localhost";
      port = 54545;
      ssl.enabled = false;
      ssl.redirecttohttps = false;
      cors.alloworigins = ["${cfg.instanceFQDN}"];
      stream.allowedorigins = ["${cfg.instanceFQDN}"];
    };
    database = {
      dialect = "postgres";
      connection = "host=/run/postgresql dbname=${cfg.user} user=${cfg.user}";
    };
    defaultuser = {
      name = "admin";
      pass = config.sops.placeholder.${cfg.adminPasswordSopsKey};
    };
    registration = false;
  };
  server-config-yaml = lib.generators.toYAML {} server-config;
in {
  options.hive.gotify-instance = {
    enable = lib.mkEnableOption "Enable the Gotify instance";
    instanceFQDN = lib.mkOption {
      type = lib.types.singleLineStr;
      example = "gotify.example.com";
      description = "Fully qualified domain name of the Gotify instance";
    };
    user = lib.mkOption {
      type = lib.types.singleLineStr;
      default = "gotify";
      description = "The user to run the service as";
    };
    group = lib.mkOption {
      type = lib.types.singleLineStr;
      default = "gotify";
      description = "The group to run the service as";
    };
    adminPasswordSopsKey = lib.mkOption {
      type = lib.types.singleLineStr;
      description = "The SOPS key for the default admin user";
    };
  };

  config = lib.mkIf cfg.enable {
    services.gotify.enable = true;

    # Config setup
    sops.templates."gotify-server-config.yml" = {
      owner = cfg.user;
      content = server-config-yaml;
    };
    environment.etc."gotify/config.yml".source = config.sops.templates."gotify-server-config.yml".path;

    # User setup
    users.users = lib.mkIf (cfg.user == "gotify") {
      gotify = {
        description = "Gotify service";
        useDefaultShell = true;
        group = cfg.group;
        isSystemUser = true;
      };
    };

    users.groups = lib.mkIf (cfg.group == "gotify") {
      gotify = {};
    };

    # Configure gotify to run as the specified user (for postgres authentication)
    systemd.services.gotify-server = {
      serviceConfig = {
        DynamicUser = lib.mkForce false;
        User = cfg.user;
        RuntimeDirectory = "gotify";
      };
    };

    # Fallback server with only 403
    services.nginx.virtualHosts.${config.networking.domain} = lib.mkDefault {
      default = true;
      locations."/".return = 403;
      forceSSL = true;
      enableACME = true;
    };

    # Virtual host for gotify
    services.nginx.virtualHosts."${cfg.instanceFQDN}" = {
      forceSSL = true;
      enableACME = true;
      locations."/" = {
        proxyPass = "http://${server-config.server.listenaddr}:${toString server-config.server.port}";
        extraConfig = ''
          # Ensuring it can use websockets
          proxy_set_header   Upgrade $http_upgrade;
          proxy_set_header   Connection "upgrade";
          proxy_set_header   X-Real-IP $remote_addr;
          proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
          proxy_set_header   X-Forwarded-Proto http;
          proxy_redirect     http:// $scheme://;

          # The proxy must preserve the host because gotify verifies the host with the origin
          # for WebSocket connections
          proxy_set_header   Host $host;

          # These sets the timeout so that the websocket can stay alive
          proxy_connect_timeout   1m;
          proxy_send_timeout      1m;
          proxy_read_timeout      1m;
        '';
      };
    };

    # Database setup
    services.postgresql = {
      enable = true;
      ensureDatabases = [cfg.user];
      ensureUsers = [
        {
          name = cfg.user;
          ensureDBOwnership = true;
        }
      ];
    };
  };
}
