1# verifies:
2# 1. nginx generates config file with shared http context definitions above
3# generated virtual hosts config.
4# 2. whether the ETag header is properly generated whenever we're serving
5# files in Nix store paths
6# 3. nginx doesn't restart on configuration changes (only reloads)
7{ pkgs, ... }:
8{
9 name = "nginx";
10 meta = with pkgs.lib.maintainers; {
11 maintainers = [
12 mbbx6spp
13 danbst
14 ];
15 };
16
17 nodes = {
18 webserver =
19 { pkgs, lib, ... }:
20 {
21 services.nginx.enable = true;
22 services.nginx.commonHttpConfig = ''
23 log_format ceeformat '@cee: {"status":"$status",'
24 '"request_time":$request_time,'
25 '"upstream_response_time":$upstream_response_time,'
26 '"pipe":"$pipe","bytes_sent":$bytes_sent,'
27 '"connection":"$connection",'
28 '"remote_addr":"$remote_addr",'
29 '"host":"$host",'
30 '"timestamp":"$time_iso8601",'
31 '"request":"$request",'
32 '"http_referer":"$http_referer",'
33 '"upstream_addr":"$upstream_addr"}';
34 '';
35 services.nginx.virtualHosts."0.my.test" = {
36 extraConfig = ''
37 access_log syslog:server=unix:/dev/log,facility=user,tag=mytag,severity=info ceeformat;
38 location /favicon.ico { allow all; access_log off; log_not_found off; }
39 '';
40 };
41
42 services.nginx.virtualHosts.localhost = {
43 root = pkgs.runCommand "testdir" { } ''
44 mkdir "$out"
45 echo hello world > "$out/index.html"
46 '';
47 };
48
49 services.nginx.enableReload = true;
50
51 specialisation.etagSystem.configuration = {
52 services.nginx.virtualHosts.localhost = {
53 root = lib.mkForce (
54 pkgs.runCommand "testdir2" { } ''
55 mkdir "$out"
56 echo content changed > "$out/index.html"
57 ''
58 );
59 };
60 };
61
62 specialisation.justReloadSystem.configuration = {
63 services.nginx.virtualHosts."1.my.test".listen = [
64 {
65 addr = "127.0.0.1";
66 port = 8080;
67 }
68 ];
69 };
70
71 specialisation.reloadRestartSystem.configuration = {
72 services.nginx.package = pkgs.nginxMainline;
73 };
74
75 specialisation.reloadWithErrorsSystem.configuration = {
76 services.nginx.package = pkgs.nginxMainline;
77 services.nginx.virtualHosts."!@$$(#*%".locations."~@#*$*!)".proxyPass = ";;;";
78 };
79 };
80 };
81
82 testScript =
83 { nodes, ... }:
84 let
85 etagSystem = "${nodes.webserver.system.build.toplevel}/specialisation/etagSystem";
86 justReloadSystem = "${nodes.webserver.system.build.toplevel}/specialisation/justReloadSystem";
87 reloadRestartSystem = "${nodes.webserver.system.build.toplevel}/specialisation/reloadRestartSystem";
88 reloadWithErrorsSystem = "${nodes.webserver.system.build.toplevel}/specialisation/reloadWithErrorsSystem";
89 in
90 ''
91 url = "http://localhost/index.html"
92
93
94 def check_etag():
95 etag = webserver.succeed(
96 f'curl -v {url} 2>&1 | sed -n -e "s/^< etag: *//ip"'
97 ).rstrip()
98 http_code = webserver.succeed(
99 f"curl -w '%{{http_code}}' --head --fail -H 'If-None-Match: {etag}' {url}"
100 )
101 assert http_code.split("\n")[-1] == "304"
102
103 return etag
104
105
106 def wait_for_nginx_on_port(port):
107 webserver.wait_for_unit("nginx")
108 webserver.wait_for_open_port(port)
109
110
111 # nginx can be ready before multi-user.target, in which case switching to
112 # a different configuration might not realize it needs to restart nginx.
113 webserver.wait_for_unit("multi-user.target")
114
115 wait_for_nginx_on_port(80)
116
117 with subtest("check ETag if serving Nix store paths"):
118 old_etag = check_etag()
119 webserver.succeed(
120 "${etagSystem}/bin/switch-to-configuration test >&2"
121 )
122 wait_for_nginx_on_port(80)
123 new_etag = check_etag()
124 assert old_etag != new_etag
125
126 with subtest("config is reloaded on nixos-rebuild switch"):
127 webserver.succeed(
128 "${justReloadSystem}/bin/switch-to-configuration test >&2"
129 )
130 wait_for_nginx_on_port(8080)
131 webserver.fail("journalctl -u nginx | grep -q -i stopped")
132 webserver.succeed("journalctl -u nginx | grep -q -i reloaded")
133
134 with subtest("restart when nginx package changes"):
135 webserver.succeed(
136 "${reloadRestartSystem}/bin/switch-to-configuration test >&2"
137 )
138 wait_for_nginx_on_port(80)
139 webserver.succeed("journalctl -u nginx | grep -q -i stopped")
140
141 with subtest("nixos-rebuild --switch should fail when there are configuration errors"):
142 webserver.fail(
143 "${reloadWithErrorsSystem}/bin/switch-to-configuration test >&2"
144 )
145 webserver.succeed("[[ $(systemctl is-failed nginx-config-reload) == failed ]]")
146 webserver.succeed("[[ $(systemctl is-failed nginx) == active ]]")
147 # just to make sure operation is idempotent. During development I had a situation
148 # when first time it shows error, but stops showing it on subsequent rebuilds
149 webserver.fail(
150 "${reloadWithErrorsSystem}/bin/switch-to-configuration test >&2"
151 )
152 '';
153}