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