diff --git a/nixos/modules/system/boot/systemd.nix b/nixos/modules/system/boot/systemd.nix index 26ee1c46caf4..e010181eba13 100644 --- a/nixos/modules/system/boot/systemd.nix +++ b/nixos/modules/system/boot/systemd.nix @@ -208,6 +208,10 @@ let ++ [ "systemd-exit.service" "systemd-update-done.service" + + # Capsule support + "capsule@.service" + "capsule.slice" ] ++ cfg.additionalUpstreamSystemUnits; diff --git a/nixos/modules/system/boot/systemd/user.nix b/nixos/modules/system/boot/systemd/user.nix index 802893cecf13..48903742a919 100644 --- a/nixos/modules/system/boot/systemd/user.nix +++ b/nixos/modules/system/boot/systemd/user.nix @@ -30,6 +30,7 @@ let "background.slice" "basic.target" "bluetooth.target" + "capsule@.target" "default.target" "exit.target" "graphical-session-pre.target" diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 93243f29082a..466fff654e07 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -1411,6 +1411,7 @@ in systemd-binfmt = handleTestOn [ "x86_64-linux" ] ./systemd-binfmt.nix { }; systemd-boot = handleTest ./systemd-boot.nix { }; systemd-bpf = runTest ./systemd-bpf.nix; + systemd-capsules = runTest ./systemd-capsules.nix; systemd-confinement = handleTest ./systemd-confinement { }; systemd-coredump = runTest ./systemd-coredump.nix; systemd-credentials-tpm2 = runTest ./systemd-credentials-tpm2.nix; diff --git a/nixos/tests/systemd-capsules.nix b/nixos/tests/systemd-capsules.nix new file mode 100644 index 000000000000..d794a454a9af --- /dev/null +++ b/nixos/tests/systemd-capsules.nix @@ -0,0 +1,47 @@ +{ lib, ... }: +{ + name = "systemd-capsules"; + + meta.maintainers = with lib.maintainers; [ fpletz ]; + + nodes.machine = + { pkgs, ... }: + { + environment.systemPackages = [ pkgs.hello ]; + systemd.user.services.alice-sleep = { + wantedBy = [ "capsule@alice.target" ]; + serviceConfig = { + ExecStart = "${pkgs.coreutils}/bin/sleep 999"; + }; + }; + }; + + testScript = # python + '' + machine.wait_for_unit("multi-user.target") + + with subtest("capsule setup"): + machine.succeed("systemctl start capsule@alice.service") + + with subtest("imperative user service in capsule"): + machine.succeed("systemd-run --capsule=alice --unit=sleeptest.service /run/current-system/sw/bin/sleep 999") + machine.succeed("systemctl --capsule=alice status sleeptest.service") + + with subtest("declarative user service in capsule"): + machine.succeed("systemctl --capsule=alice status alice-sleep.service") + machine.succeed("systemctl --capsule=alice stop alice-sleep.service") + machine.fail("systemctl --capsule=alice status alice-sleep.service") + machine.succeed("systemctl --capsule=alice start alice-sleep.service") + machine.succeed("systemctl --capsule=alice status alice-sleep.service") + + with subtest("interactive shell with terminal in capsule"): + hello_output = machine.succeed("systemd-run -t --capsule=alice /run/current-system/sw/bin/bash -i -c 'hello | tee ~/hello'") + assert hello_output == "Hello, world!\r\n" + machine.copy_from_vm("/var/lib/capsules/alice/hello") + + with subtest("capsule cleanup"): + machine.succeed("systemctl --capsule=alice stop sleeptest.service") + machine.succeed("systemctl stop capsule@alice.service") + machine.succeed("systemctl clean --all capsule@alice.service") + ''; +}