1{ pkgs, ... }: 2let 3 homeserverDomain = "server"; 4 homeserverUrl = "http://server:8008"; 5 userName = "alice"; 6 botUserName = "instagrambot"; 7 8 asToken = "this-is-my-totally-randomly-generated-as-token"; 9 hsToken = "this-is-my-totally-randomly-generated-hs-token"; 10in 11{ 12 name = "mautrix-meta-postgres"; 13 meta.maintainers = pkgs.mautrix-meta.meta.maintainers; 14 15 nodes = { 16 server = 17 { config, pkgs, ... }: 18 { 19 services.postgresql = { 20 enable = true; 21 22 ensureUsers = [ 23 { 24 name = "mautrix-meta-instagram"; 25 ensureDBOwnership = true; 26 } 27 ]; 28 29 ensureDatabases = [ 30 "mautrix-meta-instagram" 31 ]; 32 }; 33 34 systemd.services.mautrix-meta-instagram = { 35 wants = [ "postgres.service" ]; 36 after = [ "postgres.service" ]; 37 }; 38 39 services.matrix-synapse = { 40 enable = true; 41 settings = { 42 database.name = "sqlite3"; 43 44 enable_registration = true; 45 46 # don't use this in production, always use some form of verification 47 enable_registration_without_verification = true; 48 49 listeners = [ 50 { 51 # The default but tls=false 52 bind_addresses = [ 53 "0.0.0.0" 54 ]; 55 port = 8008; 56 resources = [ 57 { 58 "compress" = true; 59 "names" = [ "client" ]; 60 } 61 { 62 "compress" = false; 63 "names" = [ "federation" ]; 64 } 65 ]; 66 tls = false; 67 type = "http"; 68 } 69 ]; 70 }; 71 }; 72 73 services.mautrix-meta.instances.instagram = { 74 enable = true; 75 76 environmentFile = pkgs.writeText ''my-secrets'' '' 77 AS_TOKEN=${asToken} 78 HS_TOKEN=${hsToken} 79 ''; 80 81 settings = { 82 homeserver = { 83 address = homeserverUrl; 84 domain = homeserverDomain; 85 }; 86 87 appservice = { 88 port = 8009; 89 90 as_token = "$AS_TOKEN"; 91 hs_token = "$HS_TOKEN"; 92 93 database = { 94 type = "postgres"; 95 uri = "postgres:///mautrix-meta-instagram?host=/var/run/postgresql"; 96 }; 97 98 bot.username = botUserName; 99 }; 100 101 bridge.permissions."@${userName}:server" = "user"; 102 }; 103 }; 104 105 networking.firewall.allowedTCPPorts = [ 106 8008 107 8009 108 ]; 109 }; 110 111 client = 112 { pkgs, ... }: 113 { 114 environment.systemPackages = [ 115 (pkgs.writers.writePython3Bin "do_test" 116 { 117 libraries = [ pkgs.python3Packages.matrix-nio ]; 118 flakeIgnore = [ 119 # We don't live in the dark ages anymore. 120 # Languages like Python that are whitespace heavy will overrun 121 # 79 characters.. 122 "E501" 123 ]; 124 } 125 '' 126 import sys 127 import functools 128 import asyncio 129 130 from nio import AsyncClient, RoomMessageNotice, RoomCreateResponse, RoomInviteResponse 131 132 133 async def message_callback(matrix: AsyncClient, msg: str, _r, e): 134 print("Received matrix text message: ", e) 135 assert msg in e.body 136 exit(0) # Success! 137 138 139 async def run(homeserver: str): 140 matrix = AsyncClient(homeserver) 141 response = await matrix.register("${userName}", "foobar") 142 print("Matrix register response: ", response) 143 144 # Open a DM with the bridge bot 145 response = await matrix.room_create() 146 print("Matrix create room response:", response) 147 assert isinstance(response, RoomCreateResponse) 148 room_id = response.room_id 149 150 response = await matrix.room_invite(room_id, "@${botUserName}:${homeserverDomain}") 151 assert isinstance(response, RoomInviteResponse) 152 153 callback = functools.partial( 154 message_callback, matrix, "Hello, I'm an Instagram bridge bot." 155 ) 156 matrix.add_event_callback(callback, RoomMessageNotice) 157 158 print("Waiting for matrix message...") 159 await matrix.sync_forever(timeout=30000) 160 161 162 if __name__ == "__main__": 163 asyncio.run(run(sys.argv[1])) 164 '' 165 ) 166 ]; 167 }; 168 }; 169 170 testScript = '' 171 def extract_token(data): 172 stdout = data[1] 173 stdout = stdout.strip() 174 line = stdout.split('\n')[-1] 175 return line.split(':')[-1].strip("\" '\n") 176 177 def get_token_from(token, file): 178 data = server.execute(f"cat {file} | grep {token}") 179 return extract_token(data) 180 181 def get_as_token_from(file): 182 return get_token_from("as_token", file) 183 184 def get_hs_token_from(file): 185 return get_token_from("hs_token", file) 186 187 config_yaml = "/var/lib/mautrix-meta-instagram/config.yaml" 188 registration_yaml = "/var/lib/mautrix-meta-instagram/meta-registration.yaml" 189 190 expected_as_token = "${asToken}" 191 expected_hs_token = "${hsToken}" 192 193 start_all() 194 195 with subtest("start the server"): 196 # bridge 197 server.wait_for_unit("mautrix-meta-instagram.service") 198 199 # homeserver 200 server.wait_for_unit("matrix-synapse.service") 201 202 server.wait_for_open_port(8008) 203 # Bridge only opens the port after it contacts the homeserver 204 server.wait_for_open_port(8009) 205 206 with subtest("ensure messages can be exchanged"): 207 client.succeed("do_test ${homeserverUrl} >&2") 208 209 with subtest("ensure as_token, hs_token match from environment file"): 210 as_token = get_as_token_from(config_yaml) 211 hs_token = get_hs_token_from(config_yaml) 212 as_token_registration = get_as_token_from(registration_yaml) 213 hs_token_registration = get_hs_token_from(registration_yaml) 214 215 assert as_token == expected_as_token, f"as_token in config should match the one specified (is: {as_token}, expected: {expected_as_token})" 216 assert hs_token == expected_hs_token, f"hs_token in config should match the one specified (is: {hs_token}, expected: {expected_hs_token})" 217 assert as_token_registration == expected_as_token, f"as_token in registration should match the one specified (is: {as_token_registration}, expected: {expected_as_token})" 218 assert hs_token_registration == expected_hs_token, f"hs_token in registration should match the one specified (is: {hs_token_registration}, expected: {expected_hs_token})" 219 220 with subtest("ensure as_token and hs_token stays same after restart"): 221 server.systemctl("restart mautrix-meta-instagram") 222 server.wait_for_open_port(8009) 223 224 as_token = get_as_token_from(config_yaml) 225 hs_token = get_hs_token_from(config_yaml) 226 as_token_registration = get_as_token_from(registration_yaml) 227 hs_token_registration = get_hs_token_from(registration_yaml) 228 229 assert as_token == expected_as_token, f"as_token in config should match the one specified (is: {as_token}, expected: {expected_as_token})" 230 assert hs_token == expected_hs_token, f"hs_token in config should match the one specified (is: {hs_token}, expected: {expected_hs_token})" 231 assert as_token_registration == expected_as_token, f"as_token in registration should match the one specified (is: {as_token_registration}, expected: {expected_as_token})" 232 assert hs_token_registration == expected_hs_token, f"hs_token in registration should match the one specified (is: {hs_token_registration}, expected: {expected_hs_token})" 233 ''; 234}