nixpkgs mirror (for testing)
github.com/NixOS/nixpkgs
nix
1# This tests Discourse by:
2# 1. logging in as the admin user
3# 2. sending a private message to the admin user through the API
4# 3. replying to that message via email.
5
6{
7 package,
8 pkgs,
9 lib,
10 ...
11}:
12let
13 certs = import ./common/acme/server/snakeoil-certs.nix;
14 clientDomain = "client.fake.domain";
15 discourseDomain = certs.domain;
16 adminPassword = "eYAX85qmMJ5GZIHLaXGDAoszD7HSZp5d";
17 secretKeyBase = "381f4ac6d8f5e49d804dae72aa9c046431d2f34c656a705c41cd52fed9b4f6f76f51549f0b55db3b8b0dded7a00d6a381ebe9a4367d2d44f5e743af6628b4d42";
18 admin = {
19 email = "alice@${clientDomain}";
20 username = "alice";
21 fullName = "Alice Admin";
22 passwordFile = "${pkgs.writeText "admin-pass" adminPassword}";
23 };
24in
25{
26 name = "discourse";
27 meta.maintainers = with lib.maintainers; [ talyz ];
28
29 _module.args.package = lib.mkDefault pkgs.discourse;
30
31 nodes.discourse =
32 { nodes, ... }:
33 {
34 virtualisation.memorySize = 2048;
35 virtualisation.cores = 4;
36 virtualisation.useNixStoreImage = true;
37 virtualisation.writableStore = false;
38
39 imports = [ common/user-account.nix ];
40
41 security.pki.certificateFiles = [
42 certs.ca.cert
43 ];
44
45 networking.extraHosts = ''
46 127.0.0.1 ${discourseDomain}
47 ${nodes.client.networking.primaryIPAddress} ${clientDomain}
48 '';
49
50 services.postfix = {
51 enableSubmission = true;
52 enableSubmissions = true;
53 submissionsOptions = {
54 smtpd_sasl_auth_enable = "yes";
55 smtpd_client_restrictions = "permit";
56 };
57 };
58
59 environment.systemPackages = [ pkgs.jq ];
60
61 services.postgresql.package = pkgs.postgresql_15;
62
63 services.discourse = {
64 enable = true;
65 inherit admin;
66 hostname = discourseDomain;
67 sslCertificate = "${certs.${discourseDomain}.cert}";
68 sslCertificateKey = "${certs.${discourseDomain}.key}";
69 secretKeyBaseFile = "${pkgs.writeText "secret-key-base" secretKeyBase}";
70 enableACME = false;
71 mail.outgoing.serverAddress = clientDomain;
72 mail.incoming.enable = true;
73 siteSettings = {
74 posting = {
75 min_post_length = 5;
76 min_first_post_length = 5;
77 min_personal_message_post_length = 5;
78 };
79 };
80 unicornTimeout = 900;
81 };
82
83 networking.firewall.allowedTCPPorts = [
84 25
85 465
86 ];
87 };
88
89 nodes.client =
90 { nodes, ... }:
91 {
92 imports = [ common/user-account.nix ];
93
94 security.pki.certificateFiles = [
95 certs.ca.cert
96 ];
97
98 networking.extraHosts = ''
99 127.0.0.1 ${clientDomain}
100 ${nodes.discourse.networking.primaryIPAddress} ${discourseDomain}
101 '';
102
103 services.dovecot2 = {
104 enable = true;
105 protocols = [ "imap" ];
106 };
107
108 services.postfix = {
109 enable = true;
110 origin = clientDomain;
111 relayDomains = [ clientDomain ];
112 config = {
113 compatibility_level = "2";
114 smtpd_banner = "ESMTP server";
115 myhostname = clientDomain;
116 mydestination = clientDomain;
117 };
118 };
119
120 environment.systemPackages =
121 let
122 replyToEmail = pkgs.writeScriptBin "reply-to-email" ''
123 #!${pkgs.python3.interpreter}
124 import imaplib
125 import smtplib
126 import ssl
127 import email.header
128 from email import message_from_bytes
129 from email.message import EmailMessage
130
131 with imaplib.IMAP4('localhost') as imap:
132 imap.login('alice', 'foobar')
133 imap.select()
134 status, data = imap.search(None, 'ALL')
135 assert status == 'OK'
136
137 nums = data[0].split()
138 assert len(nums) == 1
139
140 status, msg_data = imap.fetch(nums[0], '(RFC822)')
141 assert status == 'OK'
142
143 msg = email.message_from_bytes(msg_data[0][1])
144 subject = str(email.header.make_header(email.header.decode_header(msg['Subject'])))
145 reply_to = email.header.decode_header(msg['Reply-To'])[0][0]
146 message_id = email.header.decode_header(msg['Message-ID'])[0][0]
147 date = email.header.decode_header(msg['Date'])[0][0]
148
149 ctx = ssl.create_default_context()
150 with smtplib.SMTP_SSL(host='${discourseDomain}', context=ctx) as smtp:
151 reply = EmailMessage()
152 reply['Subject'] = 'Re: ' + subject
153 reply['To'] = reply_to
154 reply['From'] = 'alice@${clientDomain}'
155 reply['In-Reply-To'] = message_id
156 reply['References'] = message_id
157 reply['Date'] = date
158 reply.set_content("Test reply.")
159
160 smtp.send_message(reply)
161 smtp.quit()
162 '';
163 in
164 [ replyToEmail ];
165
166 networking.firewall.allowedTCPPorts = [ 25 ];
167 };
168
169 testScript =
170 { nodes }:
171 let
172 request = builtins.toJSON {
173 title = "Private message";
174 raw = "This is a test message.";
175 target_recipients = admin.username;
176 archetype = "private_message";
177 };
178 in
179 ''
180 discourse.start()
181 client.start()
182
183 discourse.wait_for_unit("discourse.service")
184 discourse.wait_for_file("/run/discourse/sockets/unicorn.sock")
185 discourse.wait_until_succeeds("curl -sS -f https://${discourseDomain}")
186 discourse.succeed(
187 "curl -sS -f https://${discourseDomain}/session/csrf -c cookie -b cookie -H 'Accept: application/json' | jq -r '\"X-CSRF-Token: \" + .csrf' > csrf_token",
188 "curl -sS -f https://${discourseDomain}/session -c cookie -b cookie -H @csrf_token -H 'Accept: application/json' -d 'login=${nodes.discourse.services.discourse.admin.username}' -d \"password=${adminPassword}\" | jq -e '.user.username == \"${nodes.discourse.services.discourse.admin.username}\"'",
189 "curl -sS -f https://${discourseDomain}/login -v -H 'Accept: application/json' -c cookie -b cookie 2>&1 | grep ${nodes.discourse.services.discourse.admin.username}",
190 )
191
192 client.wait_for_unit("postfix.service")
193 client.wait_for_unit("dovecot2.service")
194
195 discourse.succeed(
196 "sudo -u discourse discourse-rake api_key:create_master[master] >api_key",
197 'curl -sS -f https://${discourseDomain}/posts -X POST -H "Content-Type: application/json" -H "Api-Key: $(<api_key)" -H "Api-Username: system" -d \'${request}\' ',
198 )
199
200 client.wait_until_succeeds("reply-to-email")
201
202 discourse.wait_until_succeeds(
203 'curl -sS -f https://${discourseDomain}/topics/private-messages/system -H "Accept: application/json" -H "Api-Key: $(<api_key)" -H "Api-Username: system" | jq -e \'if .topic_list.topics[0].id != null then .topic_list.topics[0].id else null end\' >topic_id'
204 )
205 discourse.succeed(
206 'curl -sS -f https://${discourseDomain}/t/$(<topic_id) -H "Accept: application/json" -H "Api-Key: $(<api_key)" -H "Api-Username: system" | jq -e \'if .post_stream.posts[1].cooked == "<p>Test reply.</p>" then true else null end\' '
207 )
208 '';
209}