about summary refs log tree commit diff
path: root/LICENSE
Commit message (Collapse)AuthorAgeFilesLines
* feat(LICENSE): update yearsefidel2023-10-011-1/+1
|
* feat!: UI rewritesefidel2022-03-061-674/+19
|
* feat(license)!: MIT -> GPLv3sefidel2022-02-271-19/+674
|
* chore: add licensesefidel2022-02-161-0/+19
'#n75'>75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198
{ config, pkgs, lib, ... }:

with lib;

let
  dataDir = "/var/lib/mautrix-signal";
  registrationFile = "${dataDir}/signal-registration.yaml";
  cfg = config.services.mautrix-signal;
  settingsFormat = pkgs.formats.json { };
  settingsFile =
    settingsFormat.generate "mautrix-signal-config.json" cfg.settings;

in
{
  options = {
    services.mautrix-signal = {
      enable = mkEnableOption (lib.mdDoc "Mautrix-Signal, a Matrix-Signal puppeting bridge.");

      package = mkOption { type = types.package; default = pkgs.mautrix-signal; };

      settings = mkOption rec {
        apply = recursiveUpdate default;
        inherit (settingsFormat) type;
        default = {
          homeserver = {
            software = "standard";
          };

          appservice = rec {
            database = "sqlite:///${dataDir}/mautrix-signal.db";
            database_opts = { };
            hostname = "[::]";
            port = 8080;
            address = "http://localhost:${toString port}";
          };

          signal.socket_path = config.services.signald.socketPath;

          bridge = {
            permissions."*" = "relay";
            relay.whitelist = [ ];
            double_puppet_server_map = { };
            login_shared_secret_map = { };
          };

          logging = {
            version = 1;

            formatters.precise.format = "[%(levelname)s@%(name)s] %(message)s";

            handlers.console = {
              class = "logging.StreamHandler";
              formatter = "precise";
            };

            loggers = {
              mau.level = "INFO";

              # prevent tokens from leaking in the logs:
              # https://github.com/tulir/mautrix-telegram/issues/351
              aiohttp.level = "WARNING";
            };

            # log to console/systemd instead of file
            root = {
              level = "INFO";
              handlers = [ "console" ];
            };
          };
        };
        example = literalExpression ''
          {
            homeserver = {
              address = "http://localhost:8008";
              domain = "public-domain.tld";
            };

            appservice.public = {
              prefix = "/public";
              external = "https://public-appservice-address/public";
            };

            bridge.permissions = {
              "example.com" = "full";
              "@admin:example.com" = "admin";
            };
          }
        '';
        description = lib.mdDoc ''
          {file}`config.yaml` configuration as a Nix attribute set.
          Configuration options should match those described in
          [example-config.yaml](https://github.com/mautrix/signal/blob/master/signal/example-config.yaml).

          Secret tokens should be specified using {option}`environmentFile`
          instead of this world-readable attribute set.
        '';
      };

      environmentFile = mkOption {
        type = types.nullOr types.path;
        default = null;
        description = lib.mdDoc ''
          File containing environment variables to be passed to the mautrix-signal service,
          in which secret tokens can be specified securely by defining values for e.g.
          `MAUTRIX_SIGNAL_APPSERVICE_AS_TOKEN`,
          `MAUTRIX_SIGNAL_APPSERVICE_HS_TOKEN`,

          These environment variables can also be used to set other options by
          replacing hierarchy levels by `.`, converting the name to uppercase
          and prepending `MAUTRIX_SIGNAL_`.
          For example, the first value above maps to
          {option}`settings.appservice.as_token`.

          The environment variable values can be prefixed with `json::` to have
          them be parsed as JSON. For example, `login_shared_secret_map` can be
          set as follows:
          `MAUTRIX_SIGNAL_BRIDGE_LOGIN_SHARED_SECRET_MAP=json::{"example.com":"secret"}`.
        '';
      };

      serviceDependencies = mkOption {
        type = with types; listOf str;
        default = optional config.services.matrix-synapse.enable "matrix-synapse.service";
        defaultText = literalExpression ''
          optional config.services.matrix-synapse.enable "matrix-synapse.service"
        '';
        description = lib.mdDoc ''
          List of Systemd services to require and wait for when starting the application service.
        '';
      };
    };
  };

  config = mkIf cfg.enable {
    services.signald.enable = true;

    systemd.services.mautrix-signal = {
      description = "Mautrix-Signal, a Matrix-Signal puppeting bridge.";

      wantedBy = [ "multi-user.target" ];
      wants = [ "network-online.target" ] ++ cfg.serviceDependencies;
      after = [ "network-online.target" ] ++ cfg.serviceDependencies;
      path = [ pkgs.lottieconverter pkgs.ffmpeg-full ];

      # mautrix-signal tries to generate a dotfile in the home directory of
      # the running user if using a postgresql database:
      #
      #  File "python3.10/site-packages/asyncpg/connect_utils.py", line 257, in _dot_postgre>
      #    return (pathlib.Path.home() / '.postgresql' / filename).resolve()
      #  File "python3.10/pathlib.py", line 1000, in home
      #    return cls("~").expanduser()
      #  File "python3.10/pathlib.py", line 1440, in expanduser
      #    raise RuntimeError("Could not determine home directory.")
      # RuntimeError: Could not determine home directory.
      environment.HOME = dataDir;

      preStart = ''
        # generate the appservice's registration file if absent
        if [ ! -f '${registrationFile}' ]; then
          ${cfg.package}/bin/mautrix-signal \
            --generate-registration \
            --base-config='${cfg.package}/${cfg.package.pythonModule.sitePackages}/mautrix_signal/example-config.yaml' \
            --config='${settingsFile}' \
            --registration='${registrationFile}'
        fi
      '' + lib.optionalString (cfg.package ? alembic) ''
        # run automatic database init and migration scripts
        ${cfg.package.alembic}/bin/alembic -x config='${settingsFile}' upgrade head
      '';

      serviceConfig = {
        Type = "simple";
        Restart = "always";

        ProtectSystem = "strict";
        ProtectHome = true;
        ProtectKernelTunables = true;
        ProtectKernelModules = true;
        ProtectControlGroups = true;

        DynamicUser = true;
        SupplementaryGroups = [ "signald" ];
        PrivateTmp = true;
        WorkingDirectory = cfg.package; # necessary for the database migration scripts to be found
        StateDirectory = baseNameOf dataDir;
        UMask = "0027";
        EnvironmentFile = cfg.environmentFile;

        ExecStart = ''
          ${cfg.package}/bin/mautrix-signal \
            --config='${settingsFile}'
        '';
      };
    };
  };

  # meta.maintainers = with maintainers; [ boppyt ];
}