at 23.11-beta 8.4 kB view raw
1{ config, pkgs, lib, ... }: 2 3with lib; 4 5let 6 cfg = config.services.matrix-appservice-irc; 7 8 pkg = pkgs.matrix-appservice-irc; 9 bin = "${pkg}/bin/matrix-appservice-irc"; 10 11 jsonType = (pkgs.formats.json {}).type; 12 13 configFile = pkgs.runCommand "matrix-appservice-irc.yml" { 14 # Because this program will be run at build time, we need `nativeBuildInputs` 15 nativeBuildInputs = [ (pkgs.python3.withPackages (ps: [ ps.jsonschema ])) pkgs.remarshal ]; 16 preferLocalBuild = true; 17 18 config = builtins.toJSON cfg.settings; 19 passAsFile = [ "config" ]; 20 } '' 21 # The schema is given as yaml, we need to convert it to json 22 remarshal --if yaml --of json -i ${pkg}/config.schema.yml -o config.schema.json 23 python -m jsonschema config.schema.json -i $configPath 24 cp "$configPath" "$out" 25 ''; 26 registrationFile = "/var/lib/matrix-appservice-irc/registration.yml"; 27in { 28 options.services.matrix-appservice-irc = with types; { 29 enable = mkEnableOption (lib.mdDoc "the Matrix/IRC bridge"); 30 31 port = mkOption { 32 type = port; 33 description = lib.mdDoc "The port to listen on"; 34 default = 8009; 35 }; 36 37 needBindingCap = mkOption { 38 type = bool; 39 description = lib.mdDoc "Whether the daemon needs to bind to ports below 1024 (e.g. for the ident service)"; 40 default = false; 41 }; 42 43 passwordEncryptionKeyLength = mkOption { 44 type = ints.unsigned; 45 description = lib.mdDoc "Length of the key to encrypt IRC passwords with"; 46 default = 4096; 47 example = 8192; 48 }; 49 50 registrationUrl = mkOption { 51 type = str; 52 description = lib.mdDoc '' 53 The URL where the application service is listening for homeserver requests, 54 from the Matrix homeserver perspective. 55 ''; 56 example = "http://localhost:8009"; 57 }; 58 59 localpart = mkOption { 60 type = str; 61 description = lib.mdDoc "The user_id localpart to assign to the appservice"; 62 default = "appservice-irc"; 63 }; 64 65 settings = mkOption { 66 description = lib.mdDoc '' 67 Configuration for the appservice, see 68 <https://github.com/matrix-org/matrix-appservice-irc/blob/${pkgs.matrix-appservice-irc.version}/config.sample.yaml> 69 for supported values 70 ''; 71 default = {}; 72 type = submodule { 73 freeformType = jsonType; 74 75 options = { 76 homeserver = mkOption { 77 description = lib.mdDoc "Homeserver configuration"; 78 default = {}; 79 type = submodule { 80 freeformType = jsonType; 81 82 options = { 83 url = mkOption { 84 type = str; 85 description = lib.mdDoc "The URL to the home server for client-server API calls"; 86 }; 87 88 domain = mkOption { 89 type = str; 90 description = lib.mdDoc '' 91 The 'domain' part for user IDs on this home server. Usually 92 (but not always) is the "domain name" part of the homeserver URL. 93 ''; 94 }; 95 }; 96 }; 97 }; 98 99 database = mkOption { 100 default = {}; 101 description = lib.mdDoc "Configuration for the database"; 102 type = submodule { 103 freeformType = jsonType; 104 105 options = { 106 engine = mkOption { 107 type = str; 108 description = lib.mdDoc "Which database engine to use"; 109 default = "nedb"; 110 example = "postgres"; 111 }; 112 113 connectionString = mkOption { 114 type = str; 115 description = lib.mdDoc "The database connection string"; 116 default = "nedb://var/lib/matrix-appservice-irc/data"; 117 example = "postgres://username:password@host:port/databasename"; 118 }; 119 }; 120 }; 121 }; 122 123 ircService = mkOption { 124 default = {}; 125 description = lib.mdDoc "IRC bridge configuration"; 126 type = submodule { 127 freeformType = jsonType; 128 129 options = { 130 passwordEncryptionKeyPath = mkOption { 131 type = str; 132 description = lib.mdDoc '' 133 Location of the key with which IRC passwords are encrypted 134 for storage. Will be generated on first run if not present. 135 ''; 136 default = "/var/lib/matrix-appservice-irc/passkey.pem"; 137 }; 138 139 servers = mkOption { 140 type = submodule { freeformType = jsonType; }; 141 description = lib.mdDoc "IRC servers to connect to"; 142 }; 143 }; 144 }; 145 }; 146 }; 147 }; 148 }; 149 }; 150 config = mkIf cfg.enable { 151 systemd.services.matrix-appservice-irc = { 152 description = "Matrix-IRC bridge"; 153 before = [ "matrix-synapse.service" ]; # So the registration can be used by Synapse 154 after = lib.optionals (cfg.settings.database.engine == "postgres") [ 155 "postgresql.service" 156 ]; 157 wantedBy = [ "multi-user.target" ]; 158 159 preStart = '' 160 umask 077 161 # Generate key for crypting passwords 162 if ! [ -f "${cfg.settings.ircService.passwordEncryptionKeyPath}" ]; then 163 ${pkgs.openssl}/bin/openssl genpkey \ 164 -out "${cfg.settings.ircService.passwordEncryptionKeyPath}" \ 165 -outform PEM \ 166 -algorithm RSA \ 167 -pkeyopt "rsa_keygen_bits:${toString cfg.passwordEncryptionKeyLength}" 168 fi 169 # Generate registration file 170 if ! [ -f "${registrationFile}" ]; then 171 # The easy case: the file has not been generated yet 172 ${bin} --generate-registration --file ${registrationFile} --config ${configFile} --url ${cfg.registrationUrl} --localpart ${cfg.localpart} 173 else 174 # The tricky case: we already have a generation file. Because the NixOS configuration might have changed, we need to 175 # regenerate it. But this would give the service a new random ID and tokens, so we need to back up and restore them. 176 # 1. Backup 177 id=$(grep "^id:.*$" ${registrationFile}) 178 hs_token=$(grep "^hs_token:.*$" ${registrationFile}) 179 as_token=$(grep "^as_token:.*$" ${registrationFile}) 180 # 2. Regenerate 181 ${bin} --generate-registration --file ${registrationFile} --config ${configFile} --url ${cfg.registrationUrl} --localpart ${cfg.localpart} 182 # 3. Restore 183 sed -i "s/^id:.*$/$id/g" ${registrationFile} 184 sed -i "s/^hs_token:.*$/$hs_token/g" ${registrationFile} 185 sed -i "s/^as_token:.*$/$as_token/g" ${registrationFile} 186 fi 187 # Allow synapse access to the registration 188 if ${pkgs.getent}/bin/getent group matrix-synapse > /dev/null; then 189 chgrp matrix-synapse ${registrationFile} 190 chmod g+r ${registrationFile} 191 fi 192 ''; 193 194 serviceConfig = rec { 195 Type = "simple"; 196 ExecStart = "${bin} --config ${configFile} --file ${registrationFile} --port ${toString cfg.port}"; 197 198 ProtectHome = true; 199 PrivateDevices = true; 200 ProtectKernelTunables = true; 201 ProtectKernelModules = true; 202 ProtectControlGroups = true; 203 StateDirectory = "matrix-appservice-irc"; 204 StateDirectoryMode = "755"; 205 206 User = "matrix-appservice-irc"; 207 Group = "matrix-appservice-irc"; 208 209 CapabilityBoundingSet = [ "CAP_CHOWN" ] ++ optional (cfg.needBindingCap) "CAP_NET_BIND_SERVICE"; 210 AmbientCapabilities = CapabilityBoundingSet; 211 NoNewPrivileges = true; 212 213 LockPersonality = true; 214 RestrictRealtime = true; 215 PrivateMounts = true; 216 SystemCallFilter = [ 217 "@system-service @pkey" 218 "~@privileged @resources" 219 ]; 220 SystemCallArchitectures = "native"; 221 # AF_UNIX is required to connect to a postgres socket. 222 RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6"; 223 }; 224 }; 225 226 users.groups.matrix-appservice-irc = {}; 227 users.users.matrix-appservice-irc = { 228 description = "Service user for the Matrix-IRC bridge"; 229 group = "matrix-appservice-irc"; 230 isSystemUser = true; 231 }; 232 }; 233 234 # uses attributes of the linked package 235 meta.buildDocsInSandbox = false; 236}