1{ pkgs, ... }:
2let
3 homeserverUrl = "http://homeserver:8008";
4in
5{
6 name = "mautrix-discord";
7 meta.maintainers = pkgs.mautrix-discord.meta.maintainers;
8
9 nodes = {
10 homeserver =
11 { pkgs, ... }:
12 {
13 services.matrix-synapse = {
14 enable = true;
15 settings = {
16 server_name = "homeserver";
17 database.name = "sqlite3";
18
19 enable_registration = true;
20 # don't use this in production, always use some form of verification
21 enable_registration_without_verification = true;
22
23 listeners = [
24 {
25 bind_addresses = [ "0.0.0.0" ];
26 port = 8008;
27 resources = [
28 {
29 "compress" = true;
30 "names" = [ "client" ];
31 }
32 {
33 "compress" = false;
34 "names" = [ "federation" ];
35 }
36 ];
37 tls = false;
38 type = "http";
39 }
40 ];
41 };
42 };
43
44 services.mautrix-discord = {
45 enable = true;
46 registerToSynapse = true; # Enable automatic registration
47
48 settings = {
49 homeserver = {
50 address = homeserverUrl;
51 domain = "homeserver";
52 };
53
54 appservice = {
55 address = "http://homeserver:8009";
56 port = 8009;
57 id = "discord";
58 bot = {
59 username = "discordbot";
60 displayname = "Discord bridge bot";
61 avatar = "mxc://maunium.net/nIdEykemnwdisvHbpxflpDlC";
62 };
63 # These will be generated automatically
64 as_token = "generate";
65 hs_token = "generate";
66
67 database = {
68 type = "sqlite3";
69 uri = "file:/var/lib/mautrix-discord/mautrix-discord.db?_txlock=immediate";
70 };
71 };
72
73 bridge = {
74 permissions = {
75 "@alice:homeserver" = "user";
76 "*" = "relay";
77 };
78 };
79 };
80 };
81
82 networking.firewall.allowedTCPPorts = [
83 8008
84 8009
85 ];
86
87 environment.systemPackages = [
88 pkgs.nettools
89 ];
90 };
91
92 client =
93 { pkgs, ... }:
94 {
95 environment.systemPackages = [
96 (pkgs.writers.writePython3Bin "do_test"
97 {
98 libraries = [ pkgs.python3Packages.matrix-nio ];
99 flakeIgnore = [
100 "F401" # imported but unused
101 "E302" # expected 2 blank lines
102 ];
103 }
104 ''
105 import sys
106 import asyncio
107 from nio import AsyncClient, RoomMessageNotice, RoomCreateResponse
108
109
110 async def message_callback(matrix: AsyncClient, msg: str, _r, e):
111 print(f"Received message: {msg}")
112
113
114 async def run(homeserver: str):
115 client = AsyncClient(homeserver, "@test:homeserver")
116
117 # Register a new user
118 response = await client.register("test", "password123")
119 if not response.transport_response.ok:
120 print(f"Failed to register: {response}")
121 return False
122
123 # Login
124 response = await client.login("password123")
125 if not response.transport_response.ok:
126 print(f"Failed to login: {response}")
127 return False
128
129 print("Successfully logged in and basic functionality works")
130 await client.close()
131 return True
132
133
134 if __name__ == "__main__":
135 if len(sys.argv) != 2:
136 print("Usage: do_test <homeserver_url>")
137 sys.exit(1)
138
139 homeserver_url = sys.argv[1]
140 success = asyncio.run(run(homeserver_url))
141 sys.exit(0 if success else 1)
142 ''
143 )
144 ];
145 };
146 };
147
148 testScript = ''
149 start_all()
150
151 with subtest("wait for homeserver and bridge to be ready"):
152 homeserver.wait_for_unit("matrix-synapse.service")
153 homeserver.wait_for_open_port(8008)
154 homeserver.wait_for_unit("mautrix-discord.service")
155 homeserver.wait_for_open_port(8009)
156
157 with subtest("verify registration file was created"):
158 homeserver.wait_until_succeeds("test -f /var/lib/mautrix-discord/discord-registration.yaml")
159 homeserver.succeed("ls -la /var/lib/mautrix-discord/")
160
161 with subtest("verify bridge connects to homeserver"):
162 # Give the bridge a moment to connect
163 homeserver.sleep(5)
164
165 # Check that the bridge is running and listening
166 homeserver.succeed("systemctl is-active mautrix-discord.service")
167 homeserver.succeed("netstat -tlnp | grep :8009")
168
169 with subtest("test basic matrix functionality"):
170 client.succeed("do_test ${homeserverUrl} >&2")
171 '';
172}