1import ./make-test-python.nix (
2 { pkgs, lib, ... }:
3 let
4 uiPort = 1234;
5 backendPort = 5678;
6 lemmyNodeName = "server";
7 in
8 {
9 name = "lemmy";
10 meta = with lib.maintainers; {
11 maintainers = [ mightyiam ];
12 };
13
14 nodes = {
15 client = { };
16
17 "${lemmyNodeName}" = {
18 services.lemmy = {
19 enable = true;
20 ui.port = uiPort;
21 database.createLocally = true;
22 settings = {
23 hostname = "http://${lemmyNodeName}";
24 port = backendPort;
25 # Without setup, the /feeds/* and /nodeinfo/* API endpoints won't return 200
26 setup = {
27 admin_username = "mightyiam";
28 site_name = "Lemmy FTW";
29 admin_email = "mightyiam@example.com";
30 };
31 };
32 adminPasswordFile = /etc/lemmy-admin-password.txt;
33 caddy.enable = true;
34 };
35
36 environment.etc."lemmy-admin-password.txt".text = "ThisIsWhatIUseEverywhereTryIt";
37
38 networking.firewall.allowedTCPPorts = [ 80 ];
39
40 # pict-rs seems to need more than 1025114112 bytes
41 virtualisation.memorySize = 2000;
42 };
43 };
44
45 testScript = ''
46 server = ${lemmyNodeName}
47
48 with subtest("the merged config is secure"):
49 server.wait_for_unit("lemmy.service")
50 config_permissions = server.succeed("stat --format %A /run/lemmy/config.hjson").rstrip()
51 assert config_permissions == "-rw-------", f"merged config permissions {config_permissions} are insecure"
52 directory_permissions = server.succeed("stat --format %A /run/lemmy").rstrip()
53 assert directory_permissions[5] == directory_permissions[8] == "-", "merged config can be replaced"
54
55 with subtest("the backend starts and responds"):
56 server.wait_for_open_port(${toString backendPort})
57 # wait until succeeds, it just needs few seconds for migrations, but lets give it 50s max
58 server.wait_until_succeeds("curl --fail localhost:${toString backendPort}/api/v3/site", 50)
59
60 with subtest("the UI starts and responds"):
61 server.wait_for_unit("lemmy-ui.service")
62 server.wait_for_open_port(${toString uiPort})
63 server.succeed("curl --fail localhost:${toString uiPort}")
64
65 with subtest("Lemmy-UI responds through the caddy reverse proxy"):
66 server.systemctl("start network-online.target")
67 server.wait_for_unit("network-online.target")
68 server.wait_for_unit("caddy.service")
69 server.wait_for_open_port(80)
70 body = server.execute("curl --fail --location ${lemmyNodeName}")[1]
71 assert "Lemmy" in body, f"String Lemmy not found in response for ${lemmyNodeName}: \n{body}"
72
73 with subtest("the server is exposed externally"):
74 client.systemctl("start network-online.target")
75 client.wait_for_unit("network-online.target")
76 client.succeed("curl -v --fail ${lemmyNodeName}")
77
78 with subtest("caddy correctly routes backend requests"):
79 # Make sure we are not hitting frontend
80 server.execute("systemctl stop lemmy-ui.service")
81
82 def assert_http_code(url, expected_http_code, extra_curl_args=""):
83 _, http_code = server.execute(f'curl --location --silent -o /dev/null {extra_curl_args} --fail --write-out "%{{http_code}}" {url}')
84 assert http_code == str(expected_http_code), f"expected http code {expected_http_code}, got {http_code}"
85
86 # Caddy responds with HTTP code 502 if it cannot handle the requested path
87 assert_http_code("${lemmyNodeName}/obviously-wrong-path/", 502)
88
89 assert_http_code("${lemmyNodeName}/static/js/client.js", 200)
90 assert_http_code("${lemmyNodeName}/api/v3/site", 200)
91
92 # A 404 confirms that the request goes to the backend
93 # No path can return 200 until after we upload an image to pict-rs
94 assert_http_code("${lemmyNodeName}/pictrs/", 404)
95
96 assert_http_code("${lemmyNodeName}/feeds/all.xml", 200)
97 assert_http_code("${lemmyNodeName}/nodeinfo/2.0.json", 200)
98
99 assert_http_code("${lemmyNodeName}/some-other-made-up-path/", 404, "-X POST")
100 assert_http_code("${lemmyNodeName}/some-other-path", 404, "-H 'Accept: application/activity+json'")
101 assert_http_code("${lemmyNodeName}/some-other-path", 404, "-H 'Accept: application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"'")
102 '';
103 }
104)