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