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