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