at master 7.6 kB view raw
1{ pkgs, ... }: 2let 3 homeserverUrl = "http://homeserver:8008"; 4in 5{ 6 name = "matrix-appservice-irc"; 7 meta = { 8 maintainers = pkgs.matrix-appservice-irc.meta.maintainers; 9 }; 10 11 nodes = { 12 homeserver = { 13 # We'll switch to this once the config is copied into place 14 specialisation.running.configuration = { 15 services.matrix-synapse = { 16 enable = true; 17 settings = { 18 database.name = "sqlite3"; 19 app_service_config_files = [ "/registration.yml" ]; 20 21 enable_registration = true; 22 23 # don't use this in production, always use some form of verification 24 enable_registration_without_verification = true; 25 26 listeners = [ 27 { 28 # The default but tls=false 29 bind_addresses = [ 30 "0.0.0.0" 31 ]; 32 port = 8008; 33 resources = [ 34 { 35 "compress" = true; 36 "names" = [ "client" ]; 37 } 38 { 39 "compress" = false; 40 "names" = [ "federation" ]; 41 } 42 ]; 43 tls = false; 44 type = "http"; 45 } 46 ]; 47 }; 48 }; 49 50 networking.firewall.allowedTCPPorts = [ 8008 ]; 51 }; 52 }; 53 54 ircd = { 55 services.ngircd = { 56 enable = true; 57 config = '' 58 [Global] 59 Name = ircd.ircd 60 Info = Server Info Text 61 AdminInfo1 = _ 62 63 [Channel] 64 Name = #test 65 Topic = a cool place 66 67 [Options] 68 PAM = no 69 ''; 70 }; 71 networking.firewall.allowedTCPPorts = [ 6667 ]; 72 }; 73 74 appservice = 75 { pkgs, ... }: 76 { 77 services.matrix-appservice-irc = { 78 enable = true; 79 registrationUrl = "http://appservice:8009"; 80 81 settings = { 82 homeserver.url = homeserverUrl; 83 homeserver.domain = "homeserver"; 84 85 ircService = { 86 servers."ircd" = { 87 name = "IRCd"; 88 port = 6667; 89 dynamicChannels = { 90 enabled = true; 91 aliasTemplate = "#irc_$CHANNEL"; 92 }; 93 }; 94 mediaProxy = { 95 publicUrl = "http://localhost:11111/media"; 96 ttl = 0; 97 }; 98 }; 99 }; 100 }; 101 102 networking.firewall.allowedTCPPorts = [ 8009 ]; 103 }; 104 105 client = 106 { pkgs, ... }: 107 { 108 environment.systemPackages = [ 109 (pkgs.writers.writePython3Bin "do_test" 110 { 111 libraries = [ pkgs.python3Packages.matrix-nio ]; 112 flakeIgnore = [ 113 # We don't live in the dark ages anymore. 114 # Languages like Python that are whitespace heavy will overrun 115 # 79 characters.. 116 "E501" 117 ]; 118 } 119 '' 120 import sys 121 import socket 122 import functools 123 from time import sleep 124 import asyncio 125 126 from nio import AsyncClient, RoomMessageText, JoinResponse 127 128 129 async def matrix_room_message_text_callback(matrix: AsyncClient, msg: str, _r, e): 130 print("Received matrix text message: ", e) 131 if msg in e.body: 132 print("Received hi from IRC") 133 await matrix.close() 134 exit(0) # Actual exit point 135 136 137 class IRC: 138 def __init__(self): 139 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 140 sock.connect(("ircd", 6667)) 141 sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) 142 sock.send(b"USER bob bob bob :bob\n") 143 sock.send(b"NICK bob\n") 144 self.sock = sock 145 146 def join(self, room: str): 147 self.sock.send(f"JOIN {room}\n".encode()) 148 149 def privmsg(self, room: str, msg: str): 150 self.sock.send(f"PRIVMSG {room} :{msg}\n".encode()) 151 152 def expect_msg(self, body: str): 153 buffer = "" 154 while True: 155 buf = self.sock.recv(1024).decode() 156 buffer += buf 157 if body in buffer: 158 return 159 160 161 async def run(homeserver: str): 162 irc = IRC() 163 164 matrix = AsyncClient(homeserver) 165 response = await matrix.register("alice", "foobar") 166 print("Matrix register response: ", response) 167 168 response = await matrix.join("#irc_#test:homeserver") 169 print("Matrix join room response:", response) 170 assert isinstance(response, JoinResponse) 171 room_id = response.room_id 172 173 irc.join("#test") 174 # FIXME: what are we waiting on here? Matrix? IRC? Both? 175 # 10s seem bad for busy hydra machines. 176 sleep(10) 177 178 # Exchange messages 179 print("Sending text message to matrix room") 180 response = await matrix.room_send( 181 room_id=room_id, 182 message_type="m.room.message", 183 content={"msgtype": "m.text", "body": "hi from matrix"}, 184 ) 185 print("Matrix room send response: ", response) 186 irc.privmsg("#test", "hi from irc") 187 188 print("Waiting for the matrix message to appear on the IRC side...") 189 irc.expect_msg("hi from matrix") 190 191 callback = functools.partial( 192 matrix_room_message_text_callback, matrix, "hi from irc" 193 ) 194 matrix.add_event_callback(callback, RoomMessageText) 195 196 print("Waiting for matrix message...") 197 await matrix.sync_forever() 198 199 exit(1) # Unreachable 200 201 202 if __name__ == "__main__": 203 asyncio.run(run(sys.argv[1])) 204 '' 205 ) 206 ]; 207 }; 208 }; 209 210 testScript = '' 211 import pathlib 212 import os 213 214 start_all() 215 216 ircd.wait_for_unit("ngircd.service") 217 ircd.wait_for_open_port(6667) 218 219 with subtest("start the appservice"): 220 appservice.wait_for_unit("matrix-appservice-irc.service") 221 appservice.wait_for_open_port(8009) 222 appservice.wait_for_file("/var/lib/matrix-appservice-irc/media-signingkey.jwk") 223 appservice.wait_for_open_port(11111) 224 225 with subtest("copy the registration file"): 226 appservice.copy_from_vm("/var/lib/matrix-appservice-irc/registration.yml") 227 homeserver.copy_from_host( 228 str(pathlib.Path(os.environ.get("out", os.getcwd())) / "registration.yml"), "/" 229 ) 230 homeserver.succeed("chmod 444 /registration.yml") 231 232 with subtest("start the homeserver"): 233 homeserver.succeed( 234 "/run/current-system/specialisation/running/bin/switch-to-configuration test >&2" 235 ) 236 237 homeserver.wait_for_unit("matrix-synapse.service") 238 homeserver.wait_for_open_port(8008) 239 240 with subtest("ensure messages can be exchanged"): 241 client.succeed("do_test ${homeserverUrl} >&2") 242 ''; 243}