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

let
  inherit (lib) getExe mkIf mkOption mkEnableOption optionals types;

  cfg = config.services.mollysocket;
  configuration = format.generate "mollysocket.conf" cfg.settings;
  format = pkgs.formats.toml { };
  package = pkgs.writeShellScriptBin "mollysocket" ''
    MOLLY_CONF=${configuration} exec ${getExe pkgs.mollysocket} "$@"
  '';
in {
  options.services.mollysocket = {
    enable = mkEnableOption ''
      [MollySocket](https://github.com/mollyim/mollysocket) for getting Signal
      notifications via UnifiedPush
    '';

    settings = mkOption {
      default = { };
      description = ''
        Configuration for MollySocket. Available options are listed
        [here](https://github.com/mollyim/mollysocket#configuration).
      '';
      type = types.submodule {
        freeformType = format.type;
        options = {
          host = mkOption {
            default = "127.0.0.1";
            description = "Listening address of the web server";
            type = types.str;
          };

          port = mkOption {
            default = 8020;
            description = "Listening port of the web server";
            type = types.port;
          };

          allowed_endpoints = mkOption {
            default = [ "*" ];
            description = "List of UnifiedPush servers";
            example = [ "https://ntfy.sh" ];
            type = with types; listOf str;
          };

          allowed_uuids = mkOption {
            default = [ "*" ];
            description = "UUIDs of Signal accounts that may use this server";
            example = [ "abcdef-12345-tuxyz-67890" ];
            type = with types; listOf str;
          };
        };
      };
    };

    environmentFile = mkOption {
      default = null;
      description = ''
        Environment file (see {manpage}`systemd.exec(5)` "EnvironmentFile="
        section for the syntax) passed to the service. This option can be
        used to safely include secrets in the configuration.
      '';
      example = "/run/secrets/mollysocket";
      type = with types; nullOr path;
    };

    logLevel = mkOption {
      default = "info";
      description = "Set the {env}`RUST_LOG` environment variable";
      example = "debug";
      type = types.str;
    };
  };

  config = mkIf cfg.enable {
    environment.systemPackages = [
      package
    ];

    # see https://github.com/mollyim/mollysocket/blob/main/mollysocket.service
    systemd.services.mollysocket = {
      description = "MollySocket";
      wantedBy = [ "multi-user.target" ];
      after = [ "network-online.target" ];
      wants = [ "network-online.target" ];
      environment.RUST_LOG = cfg.logLevel;
      serviceConfig = let
        capabilities = [ "" ] ++ optionals (cfg.settings.port < 1024) [ "CAP_NET_BIND_SERVICE" ];
      in {
        EnvironmentFile = cfg.environmentFile;
        ExecStart = "${getExe package} server";
        KillSignal = "SIGINT";
        Restart = "on-failure";
        StateDirectory = "mollysocket";
        TimeoutStopSec = 5;
        WorkingDirectory = "/var/lib/mollysocket";

        # hardening
        AmbientCapabilities = capabilities;
        CapabilityBoundingSet = capabilities;
        DevicePolicy = "closed";
        DynamicUser = true;
        LockPersonality = true;
        MemoryDenyWriteExecute = true;
        NoNewPrivileges = true;
        PrivateDevices = true;
        PrivateTmp = true;
        PrivateUsers = true;
        ProcSubset = "pid";
        ProtectClock = true;
        ProtectControlGroups = true;
        ProtectHome = true;
        ProtectHostname = true;
        ProtectKernelLogs = true;
        ProtectKernelModules = true;
        ProtectKernelTunables = true;
        ProtectProc = "invisible";
        ProtectSystem = "strict";
        RemoveIPC = true;
        RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
        RestrictNamespaces = true;
        RestrictRealtime = true;
        RestrictSUIDSGID = true;
        SystemCallArchitectures = "native";
        SystemCallFilter = [ "@system-service" "~@resources" "~@privileged" ];
        UMask = "0077";
      };
    };
  };

  meta.maintainers = with lib.maintainers; [ dotlambda ];
}
