1import ./make-test-python.nix (
2 { pkgs, ... }:
3 let
4
5 container =
6 { config, ... }:
7 {
8 # We re-use the NixOS container option ...
9 boot.isContainer = true;
10 # ... and revert unwanted defaults
11 networking.useHostResolvConf = false;
12
13 # use networkd to obtain systemd network setup
14 networking.useNetworkd = true;
15 networking.useDHCP = false;
16
17 # systemd-nspawn expects /sbin/init
18 boot.loader.initScript.enable = true;
19
20 imports = [ ../modules/profiles/minimal.nix ];
21
22 system.stateVersion = config.system.nixos.release;
23
24 nixpkgs.pkgs = pkgs;
25 };
26
27 containerSystem =
28 (import ../lib/eval-config.nix {
29 system = null;
30 modules = [ container ];
31 }).config.system.build.toplevel;
32
33 containerName = "container";
34 containerRoot = "/var/lib/machines/${containerName}";
35
36 containerTarball = pkgs.callPackage ../lib/make-system-tarball.nix {
37 storeContents = [
38 {
39 object = containerSystem;
40 symlink = "/nix/var/nix/profiles/system";
41 }
42 ];
43
44 contents = [
45 {
46 source = containerSystem + "/etc/os-release";
47 target = "/etc/os-release";
48 }
49 {
50 source = containerSystem + "/init";
51 target = "/sbin/init";
52 }
53 ];
54 };
55 in
56 {
57 name = "systemd-machinectl";
58
59 nodes.machine =
60 { lib, ... }:
61 {
62 # use networkd to obtain systemd network setup
63 networking.useNetworkd = true;
64 networking.useDHCP = false;
65
66 # do not try to access cache.nixos.org
67 nix.settings.substituters = lib.mkForce [ ];
68
69 # auto-start container
70 systemd.targets.machines.wants = [ "systemd-nspawn@${containerName}.service" ];
71
72 virtualisation.additionalPaths = [
73 containerSystem
74 containerTarball
75 ];
76
77 systemd.tmpfiles.rules = [
78 "d /var/lib/machines/shared-decl 0755 root root - -"
79 ];
80 systemd.nspawn.shared-decl = {
81 execConfig = {
82 Boot = false;
83 Parameters = "${containerSystem}/init";
84 };
85 filesConfig = {
86 BindReadOnly = "/nix/store";
87 };
88 };
89
90 systemd.nspawn.${containerName} = {
91 filesConfig = {
92 # workaround to fix kernel namespaces; needed for Nix sandbox
93 # https://github.com/systemd/systemd/issues/27994#issuecomment-1704005670
94 Bind = "/proc:/run/proc";
95 };
96 };
97
98 systemd.services."systemd-nspawn@${containerName}" = {
99 serviceConfig.Environment = [
100 # Disable tmpfs for /tmp
101 "SYSTEMD_NSPAWN_TMPFS_TMP=0"
102
103 # force unified cgroup delegation, which would be the default
104 # if systemd could check the capabilities of the installed systemd.
105 # see also: https://github.com/NixOS/nixpkgs/pull/198526
106 "SYSTEMD_NSPAWN_UNIFIED_HIERARCHY=1"
107 ];
108 overrideStrategy = "asDropin";
109 };
110
111 # open DHCP for container
112 networking.firewall.extraCommands = ''
113 ${pkgs.iptables}/bin/iptables -A nixos-fw -i ve-+ -p udp -m udp --dport 67 -j nixos-fw-accept
114 '';
115 };
116
117 testScript = ''
118 start_all()
119 machine.wait_for_unit("default.target");
120
121 # Test machinectl start stop of shared-decl
122 machine.succeed("machinectl start shared-decl");
123 machine.wait_until_succeeds("systemctl -M shared-decl is-active default.target");
124 machine.succeed("machinectl stop shared-decl");
125
126 # create containers root
127 machine.succeed("mkdir -p ${containerRoot}");
128
129 # start container with shared nix store by using same arguments as for systemd-nspawn@.service
130 machine.succeed("systemd-run systemd-nspawn --machine=${containerName} --network-veth -U --bind-ro=/nix/store ${containerSystem}/init")
131 machine.wait_until_succeeds("systemctl -M ${containerName} is-active default.target");
132
133 # Test machinectl stop
134 machine.succeed("machinectl stop ${containerName}");
135
136 # Install container
137 # Workaround for nixos-install
138 machine.succeed("chmod o+rx /var/lib/machines");
139 machine.succeed("nixos-install --root ${containerRoot} --system ${containerSystem} --no-channel-copy --no-root-passwd");
140
141 # Allow systemd-nspawn to apply user namespace on immutable files
142 machine.succeed("chattr -i ${containerRoot}/var/empty");
143
144 # Test machinectl start
145 machine.succeed("machinectl start ${containerName}");
146 machine.wait_until_succeeds("systemctl -M ${containerName} is-active default.target");
147
148 # Test systemd-nspawn configured unified cgroup delegation
149 # see also:
150 # https://github.com/systemd/systemd/blob/main/docs/CGROUP_DELEGATION.md#three-different-tree-setups-
151 machine.succeed('systemd-run --pty --wait -M ${containerName} /run/current-system/sw/bin/stat --format="%T" --file-system /sys/fs/cgroup > fstype')
152 machine.succeed('test $(tr -d "\\r" < fstype) = cgroup2fs')
153
154 # Test if systemd-nspawn provides a working environment for nix to build derivations
155 # https://nixos.org/guides/nix-pills/07-working-derivation
156 machine.succeed('systemd-run --pty --wait -M ${containerName} /run/current-system/sw/bin/nix-instantiate --expr \'derivation { name = "myname"; builder = "/bin/sh"; args = [ "-c" "echo foo > $out" ]; system = "${pkgs.system}"; }\' --add-root /tmp/drv')
157 machine.succeed('systemd-run --pty --wait -M ${containerName} /run/current-system/sw/bin/nix-store --option substitute false --realize /tmp/drv')
158
159 # Test nss_mymachines without nscd
160 machine.succeed('LD_LIBRARY_PATH="/run/current-system/sw/lib" getent -s hosts:mymachines hosts ${containerName}');
161
162 # Test nss_mymachines via nscd
163 machine.succeed("getent hosts ${containerName}");
164
165 # Test systemd-nspawn network configuration to container
166 machine.succeed("networkctl --json=short status ve-${containerName} | ${pkgs.jq}/bin/jq -e '.OperationalState == \"routable\"'");
167
168 # Test systemd-nspawn network configuration to host
169 machine.succeed("machinectl shell ${containerName} /run/current-system/sw/bin/networkctl --json=short status host0 | ${pkgs.jq}/bin/jq -r '.OperationalState == \"routable\"'");
170
171 # Test systemd-nspawn network configuration
172 machine.succeed("ping -n -c 1 ${containerName}");
173
174 # Test systemd-nspawn uses a user namespace
175 machine.succeed("test $(machinectl status ${containerName} | grep 'UID Shift: ' | wc -l) = 1")
176
177 # Test systemd-nspawn reboot
178 machine.succeed("machinectl shell ${containerName} /run/current-system/sw/bin/reboot");
179 machine.wait_until_succeeds("systemctl -M ${containerName} is-active default.target");
180
181 # Test machinectl reboot
182 machine.succeed("machinectl reboot ${containerName}");
183 machine.wait_until_succeeds("systemctl -M ${containerName} is-active default.target");
184
185 # Restart machine
186 machine.shutdown()
187 machine.start()
188 machine.wait_for_unit("default.target");
189
190 # Test auto-start
191 machine.succeed("machinectl show ${containerName}")
192
193 # Test machinectl stop
194 machine.succeed("machinectl stop ${containerName}");
195 machine.wait_until_succeeds("test $(systemctl is-active systemd-nspawn@${containerName}) = inactive");
196
197 # Test tmpfs for /tmp
198 machine.fail("mountpoint /tmp");
199
200 # Show to to delete the container
201 machine.succeed("chattr -i ${containerRoot}/var/empty");
202 machine.succeed("rm -rf ${containerRoot}");
203
204 # Test import tarball, start, stop and remove
205 machine.succeed("machinectl import-tar ${containerTarball}/tarball/*.tar* ${containerName}");
206 machine.succeed("machinectl start ${containerName}");
207 machine.wait_until_succeeds("systemctl -M ${containerName} is-active default.target");
208 machine.succeed("machinectl stop ${containerName}");
209 machine.wait_until_succeeds("test $(systemctl is-active systemd-nspawn@${containerName}) = inactive");
210 machine.succeed("machinectl remove ${containerName}");
211 '';
212 }
213)