1import ./make-test-python.nix ({ pkgs, lib, ... }:
2let
3 orga = "example";
4 domain = "${orga}.localdomain";
5
6 tls-cert = pkgs.runCommand "selfSignedCert" { buildInputs = [ pkgs.openssl ]; } ''
7 openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -nodes -days 36500 \
8 -subj '/CN=machine.${domain}'
9 install -D -t $out key.pem cert.pem
10 '';
11in
12{
13 name = "public-inbox";
14
15 meta.maintainers = with pkgs.lib.maintainers; [ julm ];
16
17 machine = { config, pkgs, nodes, ... }: let
18 inherit (config.services) gitolite public-inbox;
19 # Git repositories paths in Gitolite.
20 # Only their baseNameOf is used for configuring public-inbox.
21 repositories = [
22 "user/repo1"
23 "user/repo2"
24 ];
25 in
26 {
27 virtualisation.diskSize = 1 * 1024;
28 virtualisation.memorySize = 1 * 1024;
29 networking.domain = domain;
30
31 security.pki.certificateFiles = [ "${tls-cert}/cert.pem" ];
32 # If using security.acme:
33 #security.acme.certs."${domain}".postRun = ''
34 # systemctl try-restart public-inbox-nntpd public-inbox-imapd
35 #'';
36
37 services.public-inbox = {
38 enable = true;
39 postfix.enable = true;
40 openFirewall = true;
41 settings.publicinbox = {
42 css = [ "href=https://machine.${domain}/style/light.css" ];
43 nntpserver = [ "nntps://machine.${domain}" ];
44 wwwlisting = "match=domain";
45 };
46 mda = {
47 enable = true;
48 args = [ "--no-precheck" ]; # Allow Bcc:
49 };
50 http = {
51 enable = true;
52 port = "/run/public-inbox-http.sock";
53 #port = 8080;
54 args = ["-W0"];
55 mounts = [
56 "https://machine.${domain}/inbox"
57 ];
58 };
59 nntp = {
60 enable = true;
61 #port = 563;
62 args = ["-W0"];
63 cert = "${tls-cert}/cert.pem";
64 key = "${tls-cert}/key.pem";
65 };
66 imap = {
67 enable = true;
68 #port = 993;
69 args = ["-W0"];
70 cert = "${tls-cert}/cert.pem";
71 key = "${tls-cert}/key.pem";
72 };
73 inboxes = lib.recursiveUpdate (lib.genAttrs (map baseNameOf repositories) (repo: {
74 address = [
75 # Routed to the "public-inbox:" transport in services.postfix.transport
76 "${repo}@${domain}"
77 ];
78 description = ''
79 ${repo}@${domain} :
80 discussions about ${repo}.
81 '';
82 url = "https://machine.${domain}/inbox/${repo}";
83 newsgroup = "inbox.comp.${orga}.${repo}";
84 coderepo = [ repo ];
85 }))
86 {
87 repo2 = {
88 hide = [
89 "imap" # FIXME: doesn't work for IMAP as of public-inbox 1.6.1
90 "manifest"
91 "www"
92 ];
93 };
94 };
95 settings.coderepo = lib.listToAttrs (map (path: lib.nameValuePair (baseNameOf path) {
96 dir = "/var/lib/gitolite/repositories/${path}.git";
97 cgitUrl = "https://git.${domain}/${path}.git";
98 }) repositories);
99 };
100
101 # Use gitolite to store Git repositories listed in coderepo entries
102 services.gitolite = {
103 enable = true;
104 adminPubkey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJmoTOQnGqX+//us5oye8UuE+tQBx9QEM7PN13jrwgqY root@localhost";
105 };
106 systemd.services.public-inbox-httpd = {
107 serviceConfig.SupplementaryGroups = [ gitolite.group ];
108 };
109
110 # Use nginx as a reverse proxy for public-inbox-httpd
111 services.nginx = {
112 enable = true;
113 recommendedGzipSettings = true;
114 recommendedOptimisation = true;
115 recommendedTlsSettings = true;
116 recommendedProxySettings = true;
117 virtualHosts."machine.${domain}" = {
118 forceSSL = true;
119 sslCertificate = "${tls-cert}/cert.pem";
120 sslCertificateKey = "${tls-cert}/key.pem";
121 locations."/".return = "302 /inbox";
122 locations."= /inbox".return = "302 /inbox/";
123 locations."/inbox".proxyPass = "http://unix:${public-inbox.http.port}:/inbox";
124 # If using TCP instead of a Unix socket:
125 #locations."/inbox".proxyPass = "http://127.0.0.1:${toString public-inbox.http.port}/inbox";
126 # Referred to by settings.publicinbox.css
127 # See http://public-inbox.org/meta/_/text/color/
128 locations."= /style/light.css".alias = pkgs.writeText "light.css" ''
129 * { background:#fff; color:#000 }
130
131 a { color:#00f; text-decoration:none }
132 a:visited { color:#808 }
133
134 *.q { color:#008 }
135
136 *.add { color:#060 }
137 *.del {color:#900 }
138 *.head { color:#000 }
139 *.hunk { color:#960 }
140
141 .hl.num { color:#f30 } /* number */
142 .hl.esc { color:#f0f } /* escape character */
143 .hl.str { color:#f30 } /* string */
144 .hl.ppc { color:#c3c } /* preprocessor */
145 .hl.pps { color:#f30 } /* preprocessor string */
146 .hl.slc { color:#099 } /* single-line comment */
147 .hl.com { color:#099 } /* multi-line comment */
148 /* .hl.opt { color:#ccc } */ /* operator */
149 /* .hl.ipl { color:#ccc } */ /* interpolation */
150
151 /* keyword groups kw[a-z] */
152 .hl.kwa { color:#f90 }
153 .hl.kwb { color:#060 }
154 .hl.kwc { color:#f90 }
155 /* .hl.kwd { color:#ccc } */
156 '';
157 };
158 };
159
160 services.postfix = {
161 enable = true;
162 setSendmail = true;
163 #sslCert = "${tls-cert}/cert.pem";
164 #sslKey = "${tls-cert}/key.pem";
165 recipientDelimiter = "+";
166 };
167
168 environment.systemPackages = [
169 pkgs.mailutils
170 pkgs.openssl
171 ];
172
173 };
174
175 testScript = ''
176 start_all()
177 machine.wait_for_unit("multi-user.target")
178 machine.wait_for_unit("public-inbox-init.service")
179
180 # Very basic check that Gitolite can work;
181 # Gitolite is not needed for the rest of this testScript
182 machine.wait_for_unit("gitolite-init.service")
183
184 # List inboxes through public-inbox-httpd
185 machine.wait_for_unit("nginx.service")
186 machine.succeed("curl -L https://machine.${domain} | grep repo1@${domain}")
187 # The repo2 inbox is hidden
188 machine.fail("curl -L https://machine.${domain} | grep repo2@${domain}")
189 machine.wait_for_unit("public-inbox-httpd.service")
190
191 # Send a mail and read it through public-inbox-httpd
192 # Must work too when using a recipientDelimiter.
193 machine.wait_for_unit("postfix.service")
194 machine.succeed("mail -t <${pkgs.writeText "mail" ''
195 Subject: Testing mail
196 From: root@localhost
197 To: repo1+extension@${domain}
198 Message-ID: <repo1@root-1>
199 Content-Type: text/plain; charset=utf-8
200 Content-Disposition: inline
201
202 This is a testing mail.
203 ''}")
204 machine.sleep(5)
205 machine.succeed("curl -L 'https://machine.${domain}/inbox/repo1/repo1@root-1/T/#u' | grep 'This is a testing mail.'")
206
207 # Read a mail through public-inbox-imapd
208 machine.wait_for_open_port(993)
209 machine.wait_for_unit("public-inbox-imapd.service")
210 machine.succeed("openssl s_client -ign_eof -crlf -connect machine.${domain}:993 <${pkgs.writeText "imap-commands" ''
211 tag login anonymous@${domain} anonymous
212 tag SELECT INBOX.comp.${orga}.repo1.0
213 tag FETCH 1 (BODY[HEADER])
214 tag LOGOUT
215 ''} | grep '^Message-ID: <repo1@root-1>'")
216
217 # TODO: Read a mail through public-inbox-nntpd
218 #machine.wait_for_open_port(563)
219 #machine.wait_for_unit("public-inbox-nntpd.service")
220
221 # Delete a mail.
222 # Note that the use of an extension not listed in the addresses
223 # require to use --all
224 machine.succeed("curl -L https://machine.example.localdomain/inbox/repo1/repo1@root-1/raw | sudo -u public-inbox public-inbox-learn rm --all")
225 machine.fail("curl -L https://machine.example.localdomain/inbox/repo1/repo1@root-1/T/#u | grep 'This is a testing mail.'")
226 '';
227})