at 22.05-pre 8.3 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 "the Matrix/IRC bridge"; 32 33 port = mkOption { 34 type = port; 35 description = "The port to listen on"; 36 default = 8009; 37 }; 38 39 needBindingCap = mkOption { 40 type = bool; 41 description = "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 = "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 = '' 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 = "The user_id localpart to assign to the appservice"; 64 default = "appservice-irc"; 65 }; 66 67 settings = mkOption { 68 description = '' 69 Configuration for the appservice, see 70 <link xlink:href="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 = "Homeserver configuration"; 80 default = {}; 81 type = submodule { 82 freeformType = jsonType; 83 84 options = { 85 url = mkOption { 86 type = str; 87 description = "The URL to the home server for client-server API calls"; 88 }; 89 90 domain = mkOption { 91 type = str; 92 description = '' 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 = "Configuration for the database"; 104 type = submodule { 105 freeformType = jsonType; 106 107 options = { 108 engine = mkOption { 109 type = str; 110 description = "Which database engine to use"; 111 default = "nedb"; 112 example = "postgres"; 113 }; 114 115 connectionString = mkOption { 116 type = str; 117 description = "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 = "IRC bridge configuration"; 128 type = submodule { 129 freeformType = jsonType; 130 131 options = { 132 passwordEncryptionKeyPath = mkOption { 133 type = str; 134 description = '' 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 = "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 wantedBy = [ "multi-user.target" ]; 157 158 preStart = '' 159 umask 077 160 # Generate key for crypting passwords 161 if ! [ -f "${cfg.settings.ircService.passwordEncryptionKeyPath}" ]; then 162 ${pkgs.openssl}/bin/openssl genpkey \ 163 -out "${cfg.settings.ircService.passwordEncryptionKeyPath}" \ 164 -outform PEM \ 165 -algorithm RSA \ 166 -pkeyopt "rsa_keygen_bits:${toString cfg.passwordEncryptionKeyLength}" 167 fi 168 # Generate registration file 169 if ! [ -f "${registrationFile}" ]; then 170 # The easy case: the file has not been generated yet 171 ${bin} --generate-registration --file ${registrationFile} --config ${configFile} --url ${cfg.registrationUrl} --localpart ${cfg.localpart} 172 else 173 # The tricky case: we already have a generation file. Because the NixOS configuration might have changed, we need to 174 # regenerate it. But this would give the service a new random ID and tokens, so we need to back up and restore them. 175 # 1. Backup 176 id=$(grep "^id:.*$" ${registrationFile}) 177 hs_token=$(grep "^hs_token:.*$" ${registrationFile}) 178 as_token=$(grep "^as_token:.*$" ${registrationFile}) 179 # 2. Regenerate 180 ${bin} --generate-registration --file ${registrationFile} --config ${configFile} --url ${cfg.registrationUrl} --localpart ${cfg.localpart} 181 # 3. Restore 182 sed -i "s/^id:.*$/$id/g" ${registrationFile} 183 sed -i "s/^hs_token:.*$/$hs_token/g" ${registrationFile} 184 sed -i "s/^as_token:.*$/$as_token/g" ${registrationFile} 185 fi 186 # Allow synapse access to the registration 187 if ${getBin pkgs.glibc}/bin/getent group matrix-synapse > /dev/null; then 188 chgrp matrix-synapse ${registrationFile} 189 chmod g+r ${registrationFile} 190 fi 191 ''; 192 193 serviceConfig = rec { 194 Type = "simple"; 195 ExecStart = "${bin} --config ${configFile} --file ${registrationFile} --port ${toString cfg.port}"; 196 197 ProtectHome = true; 198 PrivateDevices = true; 199 ProtectKernelTunables = true; 200 ProtectKernelModules = true; 201 ProtectControlGroups = true; 202 StateDirectory = "matrix-appservice-irc"; 203 StateDirectoryMode = "755"; 204 205 User = "matrix-appservice-irc"; 206 Group = "matrix-appservice-irc"; 207 208 CapabilityBoundingSet = [ "CAP_CHOWN" ] ++ optional (cfg.needBindingCap) "CAP_NET_BIND_SERVICE"; 209 AmbientCapabilities = CapabilityBoundingSet; 210 NoNewPrivileges = true; 211 212 LockPersonality = true; 213 RestrictRealtime = true; 214 PrivateMounts = true; 215 SystemCallFilter = "~@aio @clock @cpu-emulation @debug @keyring @memlock @module @mount @obsolete @raw-io @setuid @swap"; 216 SystemCallArchitectures = "native"; 217 # AF_UNIX is required to connect to a postgres socket. 218 RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6"; 219 }; 220 }; 221 222 users.groups.matrix-appservice-irc = {}; 223 users.users.matrix-appservice-irc = { 224 description = "Service user for the Matrix-IRC bridge"; 225 group = "matrix-appservice-irc"; 226 isSystemUser = true; 227 }; 228 }; 229}