From 19f36491f2e2a5029ac396c10408d653cad6c81b Mon Sep 17 00:00:00 2001 From: Ophestra Date: Tue, 6 Oct 2026 22:09:47 +0900 Subject: test: remove obsolete code Nix stuff is being removed, most of the flake.nix stuff is no longer used by anything, so remove them here. This change also replaces a hardcoded gcc command. Signed-off-by: Ophestra --- .gitea/workflows/test.yml | 2 +- ext/mksysnum_linux.pl | 2 +- internal/workflows/step.go | 2 +- internal/workflows/test.go | 26 +-- test/configuration.nix | 252 ------------------------- test/default.nix | 81 -------- test/flake.lock | 49 ----- test/flake.nix | 167 ----------------- test/hakurei/configuration.nix | 252 +++++++++++++++++++++++++ test/hakurei/default.nix | 81 ++++++++ test/hakurei/flake.lock | 49 +++++ test/hakurei/flake.nix | 76 ++++++++ test/hakurei/hsu.nix | 23 +++ test/hakurei/nixos.nix | 407 +++++++++++++++++++++++++++++++++++++++++ test/hakurei/options.nix | 364 ++++++++++++++++++++++++++++++++++++ test/hakurei/package.nix | 144 +++++++++++++++ test/hakurei/test.py | 315 +++++++++++++++++++++++++++++++ test/hsu.nix | 23 --- test/nixos.nix | 407 ----------------------------------------- test/options.nix | 364 ------------------------------------ test/package.nix | 144 --------------- test/test.py | 315 ------------------------------- 22 files changed, 1716 insertions(+), 1829 deletions(-) delete mode 100644 test/configuration.nix delete mode 100644 test/default.nix delete mode 100644 test/flake.lock delete mode 100644 test/flake.nix create mode 100644 test/hakurei/configuration.nix create mode 100644 test/hakurei/default.nix create mode 100644 test/hakurei/flake.lock create mode 100644 test/hakurei/flake.nix create mode 100644 test/hakurei/hsu.nix create mode 100644 test/hakurei/nixos.nix create mode 100644 test/hakurei/options.nix create mode 100644 test/hakurei/package.nix create mode 100644 test/hakurei/test.py delete mode 100644 test/hsu.nix delete mode 100644 test/nixos.nix delete mode 100644 test/options.nix delete mode 100644 test/package.nix delete mode 100644 test/test.py diff --git a/.gitea/workflows/test.yml b/.gitea/workflows/test.yml index e4736310..099a3c98 100644 --- a/.gitea/workflows/test.yml +++ b/.gitea/workflows/test.yml @@ -1 +1 @@ -{"name":"Test","on":["push"],"jobs":{"hakurei":{"name":"Hakurei (legacy)","runs-on":"nix","steps":[{"name":"Checkout","uses":"actions/checkout@v4"},{"name":"Run NixOS test","run":"nix build --out-link result --print-out-paths --print-build-logs ./test#checks.x86_64-linux.hakurei"},{"name":"Upload test output","uses":"actions/upload-artifact@v3","with":{"name":"hakurei-vm-output","path":"result/*","retention-days":1}}]},"race":{"name":"Hakurei (legacy with race instrument)","runs-on":"nix","steps":[{"name":"Checkout","uses":"actions/checkout@v4"},{"name":"Run NixOS test","run":"nix build --out-link result --print-out-paths --print-build-logs ./test#checks.x86_64-linux.race"},{"name":"Upload test output","uses":"actions/upload-artifact@v3","with":{"name":"hakurei-race-vm-output","path":"result/*","retention-days":1}}]},"sandbox":{"name":"Sandbox","runs-on":"rosa","steps":[{"name":"Fix container filesystem","run":"rm /var/run \u0026\u0026 ln -sf ../run /var"},{"name":"Checkout","uses":"actions/checkout@v4"},{"name":"Set up Go toolchain","uses":"actions/setup-go@v6","with":{"go-version-file":"go.mod"}},{"name":"Install packages","uses":"awalsh128/cache-apt-pkgs-action@v1","with":{"add-repository":"ppa:savoury1/pipewire","packages":"libmount-dev sway xwayland xdg-dbus-proxy pipewire","version":0,"execute_install_scripts":true}},{"name":"Request distribution","id":"dist","run":"HAKUREI_REV=\"$(git rev-parse --short HEAD)\" \u0026\u0026 /rosa/bin/mbf ci dist -o result . \"$(cat cmd/dist/VERSION)-$HAKUREI_REV\" \u0026\u0026 echo \"rev=$HAKUREI_REV\" \u003e\u003e \"$GITHUB_OUTPUT\""},{"name":"Install hakurei","run":"HAKUREI_VERSION=\"$(cat cmd/dist/VERSION)-${{ steps.dist.outputs.rev }}\" \u0026\u0026 tar xf result/hakurei-$HAKUREI_VERSION*-amd64.tar.gz \u0026\u0026 ./hakurei-$HAKUREI_VERSION*-amd64/install.sh \u0026\u0026 sudo -u ubuntu hakurei version \u0026\u0026 mkdir /var/empty"},{"name":"Compile and run test suite","run":"rm -rf result \u0026\u0026 go run -tags=testsuite ./test/sandbox"}]},"sandbox-race":{"name":"Sandbox (with race instrument)","runs-on":"rosa","steps":[{"name":"Fix container filesystem","run":"rm /var/run \u0026\u0026 ln -sf ../run /var"},{"name":"Checkout","uses":"actions/checkout@v4"},{"name":"Set up Go toolchain","uses":"actions/setup-go@v6","with":{"go-version-file":"go.mod"}},{"name":"Install packages","uses":"awalsh128/cache-apt-pkgs-action@v1","with":{"add-repository":"ppa:savoury1/pipewire","packages":"libmount-dev sway xwayland xdg-dbus-proxy pipewire","version":0,"execute_install_scripts":true}},{"name":"Request distribution","id":"dist","run":"HAKUREI_REV=\"$(git rev-parse --short HEAD)\" \u0026\u0026 /rosa/bin/mbf ci race -o result . \"$(cat cmd/dist/VERSION)-$HAKUREI_REV\" \u0026\u0026 echo \"rev=$HAKUREI_REV\" \u003e\u003e \"$GITHUB_OUTPUT\""},{"name":"Install hakurei","run":"HAKUREI_VERSION=\"$(cat cmd/dist/VERSION)-${{ steps.dist.outputs.rev }}\" \u0026\u0026 tar xf result/hakurei-$HAKUREI_VERSION*-amd64.tar.gz \u0026\u0026 ./hakurei-$HAKUREI_VERSION*-amd64/install.sh \u0026\u0026 sudo -u ubuntu hakurei version \u0026\u0026 mkdir /var/empty"},{"name":"Compile and run test suite","run":"rm -rf result \u0026\u0026 go run -tags=testsuite ./test/sandbox"}]},"sharefs":{"name":"ShareFS","runs-on":"rosa","steps":[{"name":"Fix container filesystem","run":"rm /var/run \u0026\u0026 ln -sf ../run /var"},{"name":"Checkout","uses":"actions/checkout@v4"},{"name":"Set up Go toolchain","uses":"actions/setup-go@v6","with":{"go-version-file":"go.mod"}},{"name":"Install packages","uses":"awalsh128/cache-apt-pkgs-action@v1","with":{"add-repository":"","packages":"fuse3 fsmark","version":0,"execute_install_scripts":true}},{"name":"Request distribution","id":"dist","run":"HAKUREI_REV=\"$(git rev-parse --short HEAD)\" \u0026\u0026 /rosa/bin/mbf ci dist -o result . \"$(cat cmd/dist/VERSION)-$HAKUREI_REV\" \u0026\u0026 echo \"rev=$HAKUREI_REV\" \u003e\u003e \"$GITHUB_OUTPUT\""},{"name":"Install hakurei","run":"HAKUREI_VERSION=\"$(cat cmd/dist/VERSION)-${{ steps.dist.outputs.rev }}\" \u0026\u0026 tar xf result/hakurei-$HAKUREI_VERSION*-amd64.tar.gz \u0026\u0026 ./hakurei-$HAKUREI_VERSION*-amd64/install.sh \u0026\u0026 sudo -u ubuntu hakurei version \u0026\u0026 mkdir /var/empty"},{"name":"Mount sharefs","run":"useradd -ru 1023 -md /var/lib/sdcard -k /var/empty -s /sbin/nologin media_rw \u0026\u0026 install -dm0 /sdcard \u0026\u0026 sharefs -o rw,noexec,nosuid,nodev,noatime,allow_other,mkdir,source=/var/lib/sdcard,setuid=1023,setgid=1023 /sdcard"},{"name":"Compile and run test suite","run":"sharefs -V \u0026\u0026 rm -rf result \u0026\u0026 go run -tags=testsuite ./test/sharefs"},{"name":"Upload test output","uses":"actions/upload-artifact@v3","with":{"name":"fs_mark","path":"result/*","retention-days":1}}]},"check":{"name":"Flake checks","needs":["hakurei","race"],"runs-on":"nix","steps":[{"name":"Checkout","uses":"actions/checkout@v4"},{"name":"Run checks","run":"nix --print-build-logs --experimental-features 'nix-command flakes' flake check ./test"}]},"dist":{"name":"Create distribution","runs-on":"rosa","steps":[{"name":"Fix container filesystem","run":"rm /var/run \u0026\u0026 ln -sf ../run /var"},{"name":"Checkout","uses":"actions/checkout@v4"},{"name":"Request distribution","id":"dist-test","run":"HAKUREI_REV=\"$(git rev-parse --short HEAD)\" \u0026\u0026 /rosa/bin/mbf ci dist -o result . \"$(cat cmd/dist/VERSION)-$HAKUREI_REV\" \u0026\u0026 echo \"rev=$HAKUREI_REV\" \u003e\u003e \"$GITHUB_OUTPUT\""},{"name":"Upload distribution","uses":"actions/upload-artifact@v3","with":{"name":"dist-${{ steps.dist-test.outputs.rev }}","path":"result/*","retention-days":1}}]}}} +{"name":"Test","on":["push"],"jobs":{"hakurei-legacy":{"name":"Hakurei (legacy)","runs-on":"nix","steps":[{"name":"Checkout","uses":"actions/checkout@v4"},{"name":"Run NixOS test","run":"nix build --out-link result --print-out-paths --print-build-logs ./test/hakurei#checks.x86_64-linux.hakurei"},{"name":"Upload test output","uses":"actions/upload-artifact@v3","with":{"name":"hakurei-vm-output","path":"result/*","retention-days":1}}]},"hakurei-race-legacy":{"name":"Hakurei (legacy with race instrument)","runs-on":"nix","steps":[{"name":"Checkout","uses":"actions/checkout@v4"},{"name":"Run NixOS test","run":"nix build --out-link result --print-out-paths --print-build-logs ./test/hakurei#checks.x86_64-linux.race"},{"name":"Upload test output","uses":"actions/upload-artifact@v3","with":{"name":"hakurei-race-vm-output","path":"result/*","retention-days":1}}]},"sandbox":{"name":"Sandbox","runs-on":"rosa","steps":[{"name":"Fix container filesystem","run":"rm /var/run \u0026\u0026 ln -sf ../run /var"},{"name":"Checkout","uses":"actions/checkout@v4"},{"name":"Set up Go toolchain","uses":"actions/setup-go@v6","with":{"go-version-file":"go.mod"}},{"name":"Install packages","uses":"awalsh128/cache-apt-pkgs-action@v1","with":{"add-repository":"ppa:savoury1/pipewire","packages":"libmount-dev sway xwayland xdg-dbus-proxy pipewire","version":0,"execute_install_scripts":true}},{"name":"Request distribution","id":"dist","run":"HAKUREI_REV=\"$(git rev-parse --short HEAD)\" \u0026\u0026 /rosa/bin/mbf ci dist -o result . \"$(cat cmd/dist/VERSION)-$HAKUREI_REV\" \u0026\u0026 echo \"rev=$HAKUREI_REV\" \u003e\u003e \"$GITHUB_OUTPUT\""},{"name":"Install hakurei","run":"HAKUREI_VERSION=\"$(cat cmd/dist/VERSION)-${{ steps.dist.outputs.rev }}\" \u0026\u0026 tar xf result/hakurei-$HAKUREI_VERSION*-amd64.tar.gz \u0026\u0026 ./hakurei-$HAKUREI_VERSION*-amd64/install.sh \u0026\u0026 sudo -u ubuntu hakurei version \u0026\u0026 mkdir /var/empty"},{"name":"Compile and run test suite","run":"rm -rf result \u0026\u0026 go run -tags=testsuite ./test/sandbox"}]},"sandbox-race":{"name":"Sandbox (with race instrument)","runs-on":"rosa","steps":[{"name":"Fix container filesystem","run":"rm /var/run \u0026\u0026 ln -sf ../run /var"},{"name":"Checkout","uses":"actions/checkout@v4"},{"name":"Set up Go toolchain","uses":"actions/setup-go@v6","with":{"go-version-file":"go.mod"}},{"name":"Install packages","uses":"awalsh128/cache-apt-pkgs-action@v1","with":{"add-repository":"ppa:savoury1/pipewire","packages":"libmount-dev sway xwayland xdg-dbus-proxy pipewire","version":0,"execute_install_scripts":true}},{"name":"Request distribution","id":"dist","run":"HAKUREI_REV=\"$(git rev-parse --short HEAD)\" \u0026\u0026 /rosa/bin/mbf ci race -o result . \"$(cat cmd/dist/VERSION)-$HAKUREI_REV\" \u0026\u0026 echo \"rev=$HAKUREI_REV\" \u003e\u003e \"$GITHUB_OUTPUT\""},{"name":"Install hakurei","run":"HAKUREI_VERSION=\"$(cat cmd/dist/VERSION)-${{ steps.dist.outputs.rev }}\" \u0026\u0026 tar xf result/hakurei-$HAKUREI_VERSION*-amd64.tar.gz \u0026\u0026 ./hakurei-$HAKUREI_VERSION*-amd64/install.sh \u0026\u0026 sudo -u ubuntu hakurei version \u0026\u0026 mkdir /var/empty"},{"name":"Compile and run test suite","run":"rm -rf result \u0026\u0026 go run -tags=testsuite ./test/sandbox"}]},"sharefs":{"name":"ShareFS","runs-on":"rosa","steps":[{"name":"Fix container filesystem","run":"rm /var/run \u0026\u0026 ln -sf ../run /var"},{"name":"Checkout","uses":"actions/checkout@v4"},{"name":"Set up Go toolchain","uses":"actions/setup-go@v6","with":{"go-version-file":"go.mod"}},{"name":"Install packages","uses":"awalsh128/cache-apt-pkgs-action@v1","with":{"add-repository":"","packages":"fuse3 fsmark","version":0,"execute_install_scripts":true}},{"name":"Request distribution","id":"dist","run":"HAKUREI_REV=\"$(git rev-parse --short HEAD)\" \u0026\u0026 /rosa/bin/mbf ci dist -o result . \"$(cat cmd/dist/VERSION)-$HAKUREI_REV\" \u0026\u0026 echo \"rev=$HAKUREI_REV\" \u003e\u003e \"$GITHUB_OUTPUT\""},{"name":"Install hakurei","run":"HAKUREI_VERSION=\"$(cat cmd/dist/VERSION)-${{ steps.dist.outputs.rev }}\" \u0026\u0026 tar xf result/hakurei-$HAKUREI_VERSION*-amd64.tar.gz \u0026\u0026 ./hakurei-$HAKUREI_VERSION*-amd64/install.sh \u0026\u0026 sudo -u ubuntu hakurei version \u0026\u0026 mkdir /var/empty"},{"name":"Mount sharefs","run":"useradd -ru 1023 -md /var/lib/sdcard -k /var/empty -s /sbin/nologin media_rw \u0026\u0026 install -dm0 /sdcard \u0026\u0026 sharefs -o rw,noexec,nosuid,nodev,noatime,allow_other,mkdir,source=/var/lib/sdcard,setuid=1023,setgid=1023 /sdcard"},{"name":"Compile and run test suite","run":"sharefs -V \u0026\u0026 rm -rf result \u0026\u0026 go run -tags=testsuite ./test/sharefs"},{"name":"Upload test output","uses":"actions/upload-artifact@v3","with":{"name":"fs_mark","path":"result/*","retention-days":1}}]},"dist":{"name":"Create distribution","runs-on":"rosa","steps":[{"name":"Fix container filesystem","run":"rm /var/run \u0026\u0026 ln -sf ../run /var"},{"name":"Checkout","uses":"actions/checkout@v4"},{"name":"Request distribution","id":"dist-test","run":"HAKUREI_REV=\"$(git rev-parse --short HEAD)\" \u0026\u0026 /rosa/bin/mbf ci dist -o result . \"$(cat cmd/dist/VERSION)-$HAKUREI_REV\" \u0026\u0026 echo \"rev=$HAKUREI_REV\" \u003e\u003e \"$GITHUB_OUTPUT\""},{"name":"Upload distribution","uses":"actions/upload-artifact@v3","with":{"name":"dist-${{ steps.dist-test.outputs.rev }}","path":"result/*","retention-days":1}}]}}} diff --git a/ext/mksysnum_linux.pl b/ext/mksysnum_linux.pl index 1921e7b9..22ec3fcf 100755 --- a/ext/mksysnum_linux.pl +++ b/ext/mksysnum_linux.pl @@ -55,7 +55,7 @@ sub fmt { GENERATE: my $prev; -open(GCC, "gcc -E -dD $ARGV[0] |") || die "can't run gcc"; +open(GCC, "clang -E -dD $ARGV[0] |") || die "cannot run clang"; while(){ if(/^#define __NR_Linux\s+([0-9]+)/){ # mips/mips64: extract offset diff --git a/internal/workflows/step.go b/internal/workflows/step.go index eaf8986d..93d515ae 100644 --- a/internal/workflows/step.go +++ b/internal/workflows/step.go @@ -95,6 +95,6 @@ func newNixOSTest(name string) Step { "--out-link result " + "--print-out-paths " + "--print-build-logs " + - "./test#checks.x86_64-linux." + name, + "./test/hakurei#checks.x86_64-linux." + name, } } diff --git a/internal/workflows/test.go b/internal/workflows/test.go index c81574ea..bd74511c 100644 --- a/internal/workflows/test.go +++ b/internal/workflows/test.go @@ -7,7 +7,7 @@ var _ = (&Workflow{ On: []any{"push"}, Jobs: Map[Job]{ - {"hakurei", Job{ + {"hakurei-legacy", Job{ Name: "Hakurei (legacy)", On: "nix", @@ -18,7 +18,7 @@ var _ = (&Workflow{ }, }}, - {"race", Job{ + {"hakurei-race-legacy", Job{ Name: "Hakurei (legacy with race instrument)", On: "nix", @@ -106,28 +106,6 @@ var _ = (&Workflow{ }, }}, - {"check", Job{ - Name: "Flake checks", - On: "nix", - - Needs: []string{ - "hakurei", - "race", - }, - - Steps: []Step{ - checkout, - - { - Name: "Run checks", - Run: "nix " + - "--print-build-logs " + - "--experimental-features 'nix-command flakes' " + - "flake check ./test", - }, - }, - }}, - {"dist", Job{ Name: "Create distribution", On: "rosa", diff --git a/test/configuration.nix b/test/configuration.nix deleted file mode 100644 index 62d8239d..00000000 --- a/test/configuration.nix +++ /dev/null @@ -1,252 +0,0 @@ -{ - lib, - pkgs, - config, - ... -}: -{ - users.users = { - alice = { - isNormalUser = true; - description = "Alice Foobar"; - password = "foobar"; - uid = 1000; - }; - untrusted = { - isNormalUser = true; - description = "Untrusted user"; - password = "foobar"; - uid = 1001; - - # For deny unmapped uid test: - packages = [ config.environment.hakurei.package ]; - }; - }; - - home-manager.users.alice.home.stateVersion = "24.11"; - - # Automatically login on tty1 as a normal user: - services.getty.autologinUser = "alice"; - - security.pam.loginLimits = [ - { - domain = "@users"; - item = "rtprio"; - type = "-"; - value = 1; - } - ]; - - environment = { - systemPackages = with pkgs; [ - # For D-Bus tests: - mako - libnotify - ]; - - variables = { - SWAYSOCK = "/tmp/sway-ipc.sock"; - WLR_RENDERER = "pixman"; - }; - - # To help with OCR: - etc."xdg/foot/foot.ini".text = lib.generators.toINI { } { - main = { - font = "inconsolata:size=14"; - }; - colors = rec { - foreground = "000000"; - background = "ffffff"; - regular2 = foreground; - }; - }; - }; - - fonts.packages = [ pkgs.inconsolata ]; - - # Automatically configure and start Sway when logging in on tty1: - programs.bash.loginShellInit = '' - if [ "$(tty)" = "/dev/tty1" ]; then - set -e - - mkdir -p ~/.config/sway - (sed s/Mod4/Mod1/ /etc/sway/config && - echo 'output * bg ${pkgs.nixos-artwork.wallpapers.simple-light-gray.gnomeFilePath} fill' && - echo 'output Virtual-1 res 1680x1050') > ~/.config/sway/config - - sway --validate - systemd-cat --identifier=session sway && touch /tmp/sway-exit-ok - fi - ''; - - programs.sway.enable = true; - - # For PulseAudio tests: - security.rtkit.enable = true; - services.pipewire = { - enable = true; - alsa.enable = true; - alsa.support32Bit = true; - pulse.enable = true; - jack.enable = true; - }; - - virtualisation = { - # Hopefully reduces spurious test failures: - memorySize = if pkgs.stdenv.hostPlatform.is32bit then 2046 else 8192; - - qemu.options = [ - # Need to switch to a different GPU driver than the default one (-vga std) so that Sway can launch: - "-vga none -device virtio-gpu-pci" - - # Increase Go test compiler performance: - "-smp 16" - ]; - }; - - # Disk image is too small for some tests: - boot.tmp.useTmpfs = true; - - environment.hakurei = { - enable = true; - stateDir = "/var/lib/hakurei"; - users.alice = 0; - - extraHomeConfig = - { config, ... }: - { - # To test merge deduplication: - options._hakurei.stateVersion = lib.mkOption { type = lib.types.str; }; - - config = { - home = { inherit (config._hakurei) stateVersion; }; - _hakurei.stateVersion = "23.05"; - }; - }; - - commonPaths = [ - { - type = "bind"; - src = "/var/tmp"; - write = true; - } - ]; - - apps = { - "cat.gensokyo.extern.bash.linger-timeout" = { - name = "hakurei-check-linger-timeout"; - identity = 9999; - share = pkgs.bash; - packages = [ pkgs.bash ]; - command = '' - sleep infinity & disown - exit - ''; - wait_delay = 1; - enablements = { - wayland = false; - pipewire = false; - }; - }; - - "cat.gensokyo.extern.foot.noEnablements" = { - name = "ne-foot"; - identity = 1; - shareUid = true; - verbose = true; - share = pkgs.foot; - packages = with pkgs; [ - foot - - # For wayland-info: - wayland-utils - ]; - command = "foot"; - enablements = { - dbus = false; - pipewire = false; - }; - }; - - "cat.gensokyo.extern.foot.noEnablements.immediate" = { - name = "ne-foot-immediate"; - identity = 1; - shareUid = true; - verbose = true; - wait_delay = -1; - share = pkgs.foot; - packages = [ ]; - command = "foot"; - enablements = { - dbus = false; - pipewire = false; - }; - }; - - "cat.gensokyo.extern.foot.pulseaudio" = { - name = "pa-foot"; - identity = 2; - verbose = true; - share = pkgs.foot; - packages = [ pkgs.foot ]; - command = "foot"; - enablements.dbus = false; - }; - - "cat.gensokyo.extern.Alacritty.x11" = { - name = "x11-alacritty"; - identity = 1; - shareUid = true; - verbose = true; - share = pkgs.alacritty; - packages = with pkgs; [ - # For X11 terminal emulator: - alacritty - - # For glinfo: - mesa-demos - ]; - command = "alacritty"; - enablements = { - wayland = false; - x11 = true; - dbus = false; - pipewire = false; - }; - }; - - "cat.gensokyo.extern.foot.directWayland" = { - name = "da-foot"; - identity = 4; - verbose = true; - insecureWayland = true; - share = pkgs.foot; - packages = with pkgs; [ - foot - - # For wayland-info: - wayland-utils - ]; - command = "foot"; - enablements = { - dbus = false; - pipewire = false; - }; - }; - - "cat.gensokyo.extern.strace.wantFail" = { - name = "strace-failure"; - identity = 5; - verbose = true; - share = pkgs.strace; - command = "strace true"; - enablements = { - wayland = false; - x11 = false; - dbus = false; - pipewire = false; - }; - }; - }; - }; -} diff --git a/test/default.nix b/test/default.nix deleted file mode 100644 index 81daa0a2..00000000 --- a/test/default.nix +++ /dev/null @@ -1,81 +0,0 @@ -{ - lib, - testers, - buildFHSEnv, - writeShellScriptBin, - - system, - self, - withRace ? false, -}: - -testers.nixosTest { - name = "hakurei" + (if withRace then "-race" else ""); - nodes.machine = - { options, pkgs, ... }: - let - fhs = - let - hakurei = options.environment.hakurei.package.default; - in - buildFHSEnv { - pname = "hakurei-fhs"; - inherit (hakurei) version; - targetPkgs = _: hakurei.targetPkgs; - extraOutputsToInstall = [ "dev" ]; - profile = '' - export PKG_CONFIG_PATH="/usr/share/pkgconfig:$PKG_CONFIG_PATH" - ''; - }; - in - { - environment.systemPackages = [ - # For go tests: - (writeShellScriptBin "hakurei-test" '' - # Assert hst CGO_ENABLED=0: ${ - with pkgs; - runCommand "hakurei-hst-cgo" { nativeBuildInputs = [ self.packages.${system}.hakurei.go ]; } '' - cp -r ${options.environment.hakurei.package.default.src} "$out" - chmod -R +w "$out" - cp ${writeText "hst_cgo_test.go" ''package hakurei_test;import("testing";"hakurei.app/hst");func TestTemplate(t *testing.T){hst.Template()}''} "$out/hst_cgo_test.go" - (cd "$out" && HOME="$(mktemp -d)" CGO_ENABLED=0 go test .) - '' - } - - cd ${self.packages.${system}.hakurei.src} - ${fhs}/bin/hakurei-fhs -c \ - 'CC="clang -O3 -Werror" go test --tags=noskip ${if withRace then "-race" else "-count 16"} ./...' \ - &> /tmp/hakurei-test.log && \ - touch /tmp/hakurei-test-ok - touch /tmp/hakurei-test-done - '') - ]; - - # Run with Go race detector: - environment.hakurei = lib.mkIf withRace rec { - # race detector does not support static linking - package = (pkgs.callPackage ./package.nix { }).overrideAttrs (previousAttrs: { - env = previousAttrs.env // { - GOFLAGS = previousAttrs.env.GOFLAGS + " -race"; - }; - }); - hsuPackage = options.environment.hakurei.hsuPackage.default.override { hakurei = package; }; - }; - - imports = [ - ./configuration.nix - - self.nixosModules.hakurei - self.inputs.home-manager.nixosModules.home-manager - ]; - }; - - # adapted from nixos sway integration tests - - # testScriptWithTypes:49: error: Cannot call function of unknown type - # (machine.succeed if succeed else machine.execute)( - # ^ - # Found 1 error in 1 file (checked 1 source file) - skipTypeCheck = true; - testScript = builtins.readFile ./test.py; -} diff --git a/test/flake.lock b/test/flake.lock deleted file mode 100644 index 5537506a..00000000 --- a/test/flake.lock +++ /dev/null @@ -1,49 +0,0 @@ -{ - "nodes": { - "home-manager": { - "inputs": { - "nixpkgs": [ - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1787146702, - "narHash": "sha256-YbRcLdU/yK4gWsQg7V8WTKZHfXL33g8+wSFUX3wyevs=", - "owner": "nix-community", - "repo": "home-manager", - "rev": "173b7e8d40fdc8c296a9c99854314f17a3a1704c", - "type": "github" - }, - "original": { - "owner": "nix-community", - "ref": "release-26.05", - "repo": "home-manager", - "type": "github" - } - }, - "nixpkgs": { - "locked": { - "lastModified": 1787101114, - "narHash": "sha256-gwrPcFf/rDjHPaVflbDZ040ZDmBTRj/7+s8ZmE2SaIM=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "b18a4b905f8d028dc4476412e6d6891728695379", - "type": "github" - }, - "original": { - "owner": "NixOS", - "ref": "nixos-26.05", - "repo": "nixpkgs", - "type": "github" - } - }, - "root": { - "inputs": { - "home-manager": "home-manager", - "nixpkgs": "nixpkgs" - } - } - }, - "root": "root", - "version": 7 -} diff --git a/test/flake.nix b/test/flake.nix deleted file mode 100644 index 9b18c9b6..00000000 --- a/test/flake.nix +++ /dev/null @@ -1,167 +0,0 @@ -{ - description = "hakurei container tool and nixos module"; - - inputs = { - nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05"; - - home-manager = { - url = "github:nix-community/home-manager/release-26.05"; - inputs.nixpkgs.follows = "nixpkgs"; - }; - }; - - outputs = - { - self, - nixpkgs, - home-manager, - }: - let - supportedSystems = [ - "aarch64-linux" - "i686-linux" - "x86_64-linux" - ]; - - forAllSystems = nixpkgs.lib.genAttrs supportedSystems; - nixpkgsFor = forAllSystems (system: import nixpkgs { inherit system; }); - in - { - nixosModules.hakurei = import ./nixos.nix self.packages; - - checks = forAllSystems ( - system: - let - pkgs = nixpkgsFor.${system}; - - inherit (pkgs) - runCommandLocal - callPackage - nixfmt - ; - in - { - hakurei = callPackage ./. { inherit system self; }; - race = callPackage ./. { - inherit system self; - withRace = true; - }; - } - ); - - packages = forAllSystems ( - system: - let - inherit (self.packages.${system}) hakurei hsu; - pkgs = nixpkgsFor.${system}; - in - { - default = hakurei; - hakurei = pkgs.pkgsStatic.callPackage ./package.nix { - inherit (pkgs) - # passthru.buildInputs - go_1_27 - clang - - # nativeBuildInputs - pkg-config - wayland-scanner - makeBinaryWrapper - - # appPackages - glibc - xdg-dbus-proxy - - # for check - util-linux - nettools - ; - }; - hsu = pkgs.callPackage ./hsu.nix { inherit (self.packages.${system}) hakurei; }; - sharefs = pkgs.linkFarm "sharefs" { - "bin/sharefs" = "${hakurei}/libexec/sharefs"; - "bin/mount.fuse.sharefs" = "${hakurei}/libexec/sharefs"; - }; - - dist = - pkgs.runCommand "${hakurei.name}-dist" - { - buildInputs = hakurei.targetPkgs ++ [ - pkgs.pkgsStatic.musl - ]; - } - '' - cd $(mktemp -d) \ - && cp -r ${hakurei.src}/. . \ - && chmod +w cmd && cp -r ${hsu.src}/. cmd/hsu/ \ - && chmod -R +w . - - CC="musl-clang -O3 -Werror -Qunused-arguments" \ - GOCACHE="$(mktemp -d)" \ - PATH="${pkgs.pkgsStatic.musl.bin}/bin:$PATH" \ - DESTDIR="$out" \ - ./all.sh - ''; - } - ); - - devShells = forAllSystems ( - system: - let - inherit (self.packages.${system}) hakurei; - pkgs = nixpkgsFor.${system}; - in - { - default = pkgs.mkShell { - buildInputs = hakurei.targetPkgs; - hardeningDisable = [ "fortify" ]; - }; - withPackage = pkgs.mkShell { buildInputs = [ hakurei ] ++ hakurei.targetPkgs; }; - - generateDoc = - let - inherit (pkgs) lib; - - doc = - let - eval = lib.evalModules { - specialArgs = { - inherit pkgs; - }; - modules = [ (import ./options.nix self.packages) ]; - }; - cleanEval = lib.filterAttrsRecursive (n: _: n != "_module") eval; - in - pkgs.nixosOptionsDoc { inherit (cleanEval) options; }; - docText = pkgs.runCommand "hakurei-module-docs.md" { } '' - cat ${doc.optionsCommonMark} > $out - sed -i '/*Declared by:*/,+1 d' $out - ''; - in - pkgs.mkShell { - shellHook = '' - exec cat ${docText} > options.md - ''; - }; - - generateSyscallTable = - let - GOARCH = { - x86_64-linux = "amd64"; - aarch64-linux = "arm64"; - }; - in - pkgs.mkShell { - shellHook = "exec ${pkgs.writeShellScript "generate-syscall-table" '' - set -e - ${pkgs.perl}/bin/perl \ - container/std/mksysnum_linux.pl \ - ${pkgs.linuxHeaders}/include/asm/unistd_64.h | \ - ${pkgs.go}/bin/gofmt > \ - container/std/syscall_linux_${GOARCH.${system}}.go - ''}"; - }; - } - ); - }; -} diff --git a/test/hakurei/configuration.nix b/test/hakurei/configuration.nix new file mode 100644 index 00000000..62d8239d --- /dev/null +++ b/test/hakurei/configuration.nix @@ -0,0 +1,252 @@ +{ + lib, + pkgs, + config, + ... +}: +{ + users.users = { + alice = { + isNormalUser = true; + description = "Alice Foobar"; + password = "foobar"; + uid = 1000; + }; + untrusted = { + isNormalUser = true; + description = "Untrusted user"; + password = "foobar"; + uid = 1001; + + # For deny unmapped uid test: + packages = [ config.environment.hakurei.package ]; + }; + }; + + home-manager.users.alice.home.stateVersion = "24.11"; + + # Automatically login on tty1 as a normal user: + services.getty.autologinUser = "alice"; + + security.pam.loginLimits = [ + { + domain = "@users"; + item = "rtprio"; + type = "-"; + value = 1; + } + ]; + + environment = { + systemPackages = with pkgs; [ + # For D-Bus tests: + mako + libnotify + ]; + + variables = { + SWAYSOCK = "/tmp/sway-ipc.sock"; + WLR_RENDERER = "pixman"; + }; + + # To help with OCR: + etc."xdg/foot/foot.ini".text = lib.generators.toINI { } { + main = { + font = "inconsolata:size=14"; + }; + colors = rec { + foreground = "000000"; + background = "ffffff"; + regular2 = foreground; + }; + }; + }; + + fonts.packages = [ pkgs.inconsolata ]; + + # Automatically configure and start Sway when logging in on tty1: + programs.bash.loginShellInit = '' + if [ "$(tty)" = "/dev/tty1" ]; then + set -e + + mkdir -p ~/.config/sway + (sed s/Mod4/Mod1/ /etc/sway/config && + echo 'output * bg ${pkgs.nixos-artwork.wallpapers.simple-light-gray.gnomeFilePath} fill' && + echo 'output Virtual-1 res 1680x1050') > ~/.config/sway/config + + sway --validate + systemd-cat --identifier=session sway && touch /tmp/sway-exit-ok + fi + ''; + + programs.sway.enable = true; + + # For PulseAudio tests: + security.rtkit.enable = true; + services.pipewire = { + enable = true; + alsa.enable = true; + alsa.support32Bit = true; + pulse.enable = true; + jack.enable = true; + }; + + virtualisation = { + # Hopefully reduces spurious test failures: + memorySize = if pkgs.stdenv.hostPlatform.is32bit then 2046 else 8192; + + qemu.options = [ + # Need to switch to a different GPU driver than the default one (-vga std) so that Sway can launch: + "-vga none -device virtio-gpu-pci" + + # Increase Go test compiler performance: + "-smp 16" + ]; + }; + + # Disk image is too small for some tests: + boot.tmp.useTmpfs = true; + + environment.hakurei = { + enable = true; + stateDir = "/var/lib/hakurei"; + users.alice = 0; + + extraHomeConfig = + { config, ... }: + { + # To test merge deduplication: + options._hakurei.stateVersion = lib.mkOption { type = lib.types.str; }; + + config = { + home = { inherit (config._hakurei) stateVersion; }; + _hakurei.stateVersion = "23.05"; + }; + }; + + commonPaths = [ + { + type = "bind"; + src = "/var/tmp"; + write = true; + } + ]; + + apps = { + "cat.gensokyo.extern.bash.linger-timeout" = { + name = "hakurei-check-linger-timeout"; + identity = 9999; + share = pkgs.bash; + packages = [ pkgs.bash ]; + command = '' + sleep infinity & disown + exit + ''; + wait_delay = 1; + enablements = { + wayland = false; + pipewire = false; + }; + }; + + "cat.gensokyo.extern.foot.noEnablements" = { + name = "ne-foot"; + identity = 1; + shareUid = true; + verbose = true; + share = pkgs.foot; + packages = with pkgs; [ + foot + + # For wayland-info: + wayland-utils + ]; + command = "foot"; + enablements = { + dbus = false; + pipewire = false; + }; + }; + + "cat.gensokyo.extern.foot.noEnablements.immediate" = { + name = "ne-foot-immediate"; + identity = 1; + shareUid = true; + verbose = true; + wait_delay = -1; + share = pkgs.foot; + packages = [ ]; + command = "foot"; + enablements = { + dbus = false; + pipewire = false; + }; + }; + + "cat.gensokyo.extern.foot.pulseaudio" = { + name = "pa-foot"; + identity = 2; + verbose = true; + share = pkgs.foot; + packages = [ pkgs.foot ]; + command = "foot"; + enablements.dbus = false; + }; + + "cat.gensokyo.extern.Alacritty.x11" = { + name = "x11-alacritty"; + identity = 1; + shareUid = true; + verbose = true; + share = pkgs.alacritty; + packages = with pkgs; [ + # For X11 terminal emulator: + alacritty + + # For glinfo: + mesa-demos + ]; + command = "alacritty"; + enablements = { + wayland = false; + x11 = true; + dbus = false; + pipewire = false; + }; + }; + + "cat.gensokyo.extern.foot.directWayland" = { + name = "da-foot"; + identity = 4; + verbose = true; + insecureWayland = true; + share = pkgs.foot; + packages = with pkgs; [ + foot + + # For wayland-info: + wayland-utils + ]; + command = "foot"; + enablements = { + dbus = false; + pipewire = false; + }; + }; + + "cat.gensokyo.extern.strace.wantFail" = { + name = "strace-failure"; + identity = 5; + verbose = true; + share = pkgs.strace; + command = "strace true"; + enablements = { + wayland = false; + x11 = false; + dbus = false; + pipewire = false; + }; + }; + }; + }; +} diff --git a/test/hakurei/default.nix b/test/hakurei/default.nix new file mode 100644 index 00000000..81daa0a2 --- /dev/null +++ b/test/hakurei/default.nix @@ -0,0 +1,81 @@ +{ + lib, + testers, + buildFHSEnv, + writeShellScriptBin, + + system, + self, + withRace ? false, +}: + +testers.nixosTest { + name = "hakurei" + (if withRace then "-race" else ""); + nodes.machine = + { options, pkgs, ... }: + let + fhs = + let + hakurei = options.environment.hakurei.package.default; + in + buildFHSEnv { + pname = "hakurei-fhs"; + inherit (hakurei) version; + targetPkgs = _: hakurei.targetPkgs; + extraOutputsToInstall = [ "dev" ]; + profile = '' + export PKG_CONFIG_PATH="/usr/share/pkgconfig:$PKG_CONFIG_PATH" + ''; + }; + in + { + environment.systemPackages = [ + # For go tests: + (writeShellScriptBin "hakurei-test" '' + # Assert hst CGO_ENABLED=0: ${ + with pkgs; + runCommand "hakurei-hst-cgo" { nativeBuildInputs = [ self.packages.${system}.hakurei.go ]; } '' + cp -r ${options.environment.hakurei.package.default.src} "$out" + chmod -R +w "$out" + cp ${writeText "hst_cgo_test.go" ''package hakurei_test;import("testing";"hakurei.app/hst");func TestTemplate(t *testing.T){hst.Template()}''} "$out/hst_cgo_test.go" + (cd "$out" && HOME="$(mktemp -d)" CGO_ENABLED=0 go test .) + '' + } + + cd ${self.packages.${system}.hakurei.src} + ${fhs}/bin/hakurei-fhs -c \ + 'CC="clang -O3 -Werror" go test --tags=noskip ${if withRace then "-race" else "-count 16"} ./...' \ + &> /tmp/hakurei-test.log && \ + touch /tmp/hakurei-test-ok + touch /tmp/hakurei-test-done + '') + ]; + + # Run with Go race detector: + environment.hakurei = lib.mkIf withRace rec { + # race detector does not support static linking + package = (pkgs.callPackage ./package.nix { }).overrideAttrs (previousAttrs: { + env = previousAttrs.env // { + GOFLAGS = previousAttrs.env.GOFLAGS + " -race"; + }; + }); + hsuPackage = options.environment.hakurei.hsuPackage.default.override { hakurei = package; }; + }; + + imports = [ + ./configuration.nix + + self.nixosModules.hakurei + self.inputs.home-manager.nixosModules.home-manager + ]; + }; + + # adapted from nixos sway integration tests + + # testScriptWithTypes:49: error: Cannot call function of unknown type + # (machine.succeed if succeed else machine.execute)( + # ^ + # Found 1 error in 1 file (checked 1 source file) + skipTypeCheck = true; + testScript = builtins.readFile ./test.py; +} diff --git a/test/hakurei/flake.lock b/test/hakurei/flake.lock new file mode 100644 index 00000000..5537506a --- /dev/null +++ b/test/hakurei/flake.lock @@ -0,0 +1,49 @@ +{ + "nodes": { + "home-manager": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1787146702, + "narHash": "sha256-YbRcLdU/yK4gWsQg7V8WTKZHfXL33g8+wSFUX3wyevs=", + "owner": "nix-community", + "repo": "home-manager", + "rev": "173b7e8d40fdc8c296a9c99854314f17a3a1704c", + "type": "github" + }, + "original": { + "owner": "nix-community", + "ref": "release-26.05", + "repo": "home-manager", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1787101114, + "narHash": "sha256-gwrPcFf/rDjHPaVflbDZ040ZDmBTRj/7+s8ZmE2SaIM=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "b18a4b905f8d028dc4476412e6d6891728695379", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-26.05", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "home-manager": "home-manager", + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/test/hakurei/flake.nix b/test/hakurei/flake.nix new file mode 100644 index 00000000..dc42b1cb --- /dev/null +++ b/test/hakurei/flake.nix @@ -0,0 +1,76 @@ +{ + description = "hakurei container tool and nixos module"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05"; + + home-manager = { + url = "github:nix-community/home-manager/release-26.05"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + }; + + outputs = + { + self, + nixpkgs, + home-manager, + }: + let + supportedSystems = [ "x86_64-linux" ]; + + forAllSystems = nixpkgs.lib.genAttrs supportedSystems; + nixpkgsFor = forAllSystems (system: import nixpkgs { inherit system; }); + in + { + nixosModules.hakurei = import ./nixos.nix self.packages; + + checks = forAllSystems ( + system: + let + pkgs = nixpkgsFor.${system}; + + inherit (pkgs) callPackage; + in + { + hakurei = callPackage ./. { inherit system self; }; + race = callPackage ./. { + inherit system self; + withRace = true; + }; + } + ); + + packages = forAllSystems ( + system: + let + inherit (self.packages.${system}) hakurei hsu; + pkgs = nixpkgsFor.${system}; + in + { + default = hakurei; + hakurei = pkgs.pkgsStatic.callPackage ./package.nix { + inherit (pkgs) + # passthru.buildInputs + go_1_27 + clang + + # nativeBuildInputs + pkg-config + wayland-scanner + makeBinaryWrapper + + # appPackages + glibc + xdg-dbus-proxy + + # for check + util-linux + nettools + ; + }; + hsu = pkgs.callPackage ./hsu.nix { inherit (self.packages.${system}) hakurei; }; + } + ); + }; +} diff --git a/test/hakurei/hsu.nix b/test/hakurei/hsu.nix new file mode 100644 index 00000000..5dd4cf5d --- /dev/null +++ b/test/hakurei/hsu.nix @@ -0,0 +1,23 @@ +{ + lib, + buildGoModule, + hakurei ? abort "hakurei package required", +}: + +buildGoModule { + pname = "${hakurei.pname}-hsu"; + inherit (hakurei) version; + + src = ../../cmd/hsu; + inherit (hakurei) vendorHash; + env.CGO_ENABLED = 0; + + preBuild = '' + go mod init hsu >& /dev/null + ''; + + ldflags = lib.attrsets.foldlAttrs ( + ldflags: name: value: + ldflags ++ [ "-X main.${name}=${value}" ] + ) [ "-s -w" ] { hakureiPath = "${hakurei}/libexec/hakurei"; }; +} diff --git a/test/hakurei/nixos.nix b/test/hakurei/nixos.nix new file mode 100644 index 00000000..49bfffb6 --- /dev/null +++ b/test/hakurei/nixos.nix @@ -0,0 +1,407 @@ +packages: +{ + lib, + pkgs, + config, + ... +}: + +let + inherit (lib) + lists + attrsets + mkMerge + mkIf + mapAttrs + foldlAttrs + optional + optionals + ; + + cfg = config.environment.hakurei; + + # userid*userOffset + appStart + appid + getsubuid = userid: appid: userid * 100000 + 10000 + appid; + getsubname = userid: appid: "u${toString userid}_a${toString appid}"; + getsubhome = userid: appid: "${cfg.stateDir}/u${toString userid}/a${toString appid}"; + + mountpoints = { + ${cfg.sharefs.name} = mkIf (cfg.sharefs.source != null) { + depends = [ cfg.sharefs.source ]; + device = "sharefs"; + fsType = "fuse.sharefs"; + noCheck = true; + options = [ + "rw" + "noexec" + "nosuid" + "nodev" + "noatime" + "allow_other" + "mkdir" + "source=${cfg.sharefs.source}" + "setuid=${toString config.users.users.${cfg.sharefs.user}.uid}" + "setgid=${toString config.users.groups.${cfg.sharefs.group}.gid}" + ]; + }; + }; +in + +{ + imports = [ (import ./options.nix packages) ]; + + options = { + # Forward declare a dummy option for VM filesystems since the real one won't exist + # unless the VM module is actually imported. + virtualisation.fileSystems = lib.mkOption { }; + }; + + config = mkIf cfg.enable { + assertions = [ + ( + let + conflictingApps = foldlAttrs ( + acc: id: app: + ( + acc + ++ foldlAttrs ( + acc': id': app': + if id == id' || app.shareUid && app'.shareUid || app.identity != app'.identity then acc' else acc' ++ [ id ] + ) [ ] cfg.apps + ) + ) [ ] cfg.apps; + in + { + assertion = (lists.length conflictingApps) == 0; + message = "the following hakurei apps have conflicting identities: " + (builtins.concatStringsSep ", " conflictingApps); + } + ) + ]; + + security.wrappers.hsu = { + source = "${cfg.hsuPackage}/bin/hsu"; + setuid = true; + owner = "root"; + group = "root"; + }; + + environment.etc.hsurc = { + mode = "0400"; + text = foldlAttrs ( + acc: username: fid: + "${toString config.users.users.${username}.uid} ${toString fid}\n" + acc + ) "" cfg.users; + }; + + environment.systemPackages = optional (cfg.sharefs.source != null) cfg.sharefs.package; + fileSystems = mountpoints; + virtualisation.fileSystems = mountpoints; + + home-manager = + let + privPackages = mapAttrs (_: userid: { + home.packages = foldlAttrs ( + acc: id: app: + [ + ( + let + extendDBusDefault = id: ext: { + filter = true; + + talk = [ "org.freedesktop.Notifications" ] ++ ext.talk; + own = [ + "${id}.*" + "org.mpris.MediaPlayer2.${id}.*" + ] + ++ ext.own; + + inherit (ext) call broadcast; + }; + dbusConfig = + let + default = { + talk = [ ]; + own = [ ]; + call = { }; + broadcast = { }; + }; + in + { + session_bus = if app.dbus.session != null then (app.dbus.session (extendDBusDefault id)) else (extendDBusDefault id default); + system_bus = app.dbus.system; + }; + command = if app.command == null then app.name else app.command; + script = if app.script == null then ("exec " + command + " $@") else app.script; + isGraphical = if app.gpu != null then app.gpu else app.enablements.wayland || app.enablements.x11; + + conf = { + inherit id; + inherit (app) identity enablements; + inherit (dbusConfig) session_bus system_bus; + direct_wayland = app.insecureWayland; + sched_policy = app.schedPolicy; + sched_priority = app.schedPriority; + groups = app.groups ++ optional (cfg.sharefs.source != null) cfg.sharefs.group; + + container = { + inherit (app) + wait_delay + devel + userns + device + tty + multiarch + env + ; + map_real_uid = app.mapRealUid; + host_net = app.hostNet; + host_abstract = app.hostAbstract; + share_runtime = app.shareRuntime; + share_tmpdir = app.shareTmpdir; + + filesystem = + let + bind = src: { + type = "bind"; + inherit src; + }; + optBind = src: { + type = "bind"; + inherit src; + optional = true; + }; + optDevBind = src: { + type = "bind"; + inherit src; + dev = true; + optional = true; + }; + in + [ + (bind "/bin") + (bind "/usr/bin") + (bind "/nix/store") + (optBind "/sys/block") + (optBind "/sys/bus") + (optBind "/sys/class") + (optBind "/sys/dev") + (optBind "/sys/devices") + ] + ++ optionals app.nix [ + (bind "/nix/var") + ] + ++ optionals isGraphical [ + (optDevBind "/dev/dri") + (optDevBind "/dev/nvidiactl") + (optDevBind "/dev/nvidia-modeset") + (optDevBind "/dev/nvidia-uvm") + (optDevBind "/dev/nvidia-uvm-tools") + (optDevBind "/dev/nvidia0") + ] + ++ optionals app.useCommonPaths cfg.commonPaths + ++ app.extraPaths + ++ [ + { + type = "bind"; + dst = "/etc/"; + src = "/etc/"; + special = true; + } + { + type = "link"; + dst = "/run/current-system"; + linkname = "/run/current-system"; + dereference = true; + } + ] + ++ optionals (isGraphical && config.hardware.graphics.enable) ( + [ + { + type = "link"; + dst = "/run/opengl-driver"; + linkname = config.systemd.tmpfiles.settings.graphics-driver."/run/opengl-driver"."L+".argument; + } + ] + ++ optionals (app.multiarch && config.hardware.graphics.enable32Bit) [ + { + type = "link"; + dst = "/run/opengl-driver-32"; + linkname = config.systemd.tmpfiles.settings.graphics-driver."/run/opengl-driver-32"."L+".argument; + } + ] + ) + ++ [ + { + type = "bind"; + src = getsubhome userid app.identity; + write = true; + ensure = true; + } + ]; + + username = getsubname userid app.identity; + inherit (cfg) shell; + home = getsubhome userid app.identity; + + path = + if app.path == null then + pkgs.writeScript "${app.name}-start" '' + #!${pkgs.zsh}${pkgs.zsh.shellPath} + ${script} + '' + else + app.path; + args = if app.args == null then [ "${app.name}-start" ] else app.args; + }; + }; + + checkedConfig = + name: value: + let + file = pkgs.writeText name (builtins.toJSON value); + in + pkgs.runCommand "checked-${name}" { nativeBuildInputs = [ cfg.package ]; } '' + ln -vs ${file} "$out" + hakurei show --no-store ${file} + ''; + in + pkgs.writeShellScriptBin app.name '' + exec hakurei${if app.verbose then " -v" else ""}${if app.insecureWayland then " --insecure" else ""} run ${checkedConfig "hakurei-app-${app.name}.json" conf} $@ + '' + ) + ] + ++ ( + let + pkg = if app.share != null then app.share else pkgs.${app.name}; + copy = source: "[ -d '${source}' ] && cp -Lrv '${source}' $out/share || true"; + in + optional (app.enablements.wayland || app.enablements.x11) ( + pkgs.runCommand "${app.name}-share" { } '' + mkdir -p $out/share + ${copy "${pkg}/share/applications"} + ${copy "${pkg}/share/pixmaps"} + ${copy "${pkg}/share/icons"} + ${copy "${pkg}/share/man"} + + if test -d "$out/share/applications"; then + substituteInPlace $out/share/applications/* \ + --replace-warn '${pkg}/bin/' "" \ + --replace-warn '${pkg}/libexec/' "" + fi + '' + ) + ) + ++ acc + ) [ cfg.package ] cfg.apps; + }) cfg.users; + in + { + useUserPackages = false; # prevent users.users entries from being added + + users = + mkMerge + (foldlAttrs + ( + acc: _: fid: + foldlAttrs + ( + acc: _: app: + ( + let + key = getsubname fid app.identity; + in + { + usernames = acc.usernames // { + ${key} = true; + }; + merge = acc.merge ++ [ + { + ${key} = mkMerge ( + [ + app.extraConfig + { home.packages = app.packages; } + ] + ++ lib.optional (!attrsets.hasAttrByPath [ key ] acc.usernames) cfg.extraHomeConfig + ); + } + ]; + } + ) + ) + { + inherit (acc) usernames; + merge = acc.merge ++ [ { ${getsubname fid 0} = cfg.extraHomeConfig; } ]; + } + cfg.apps + ) + { + usernames = { }; + merge = [ privPackages ]; + } + cfg.users + ).merge; + }; + + users = + let + getuser = userid: appid: { + isSystemUser = true; + createHome = true; + description = "Hakurei subordinate user ${toString appid} (u${toString userid})"; + group = getsubname userid appid; + home = getsubhome userid appid; + uid = getsubuid userid appid; + }; + getgroup = userid: appid: { gid = getsubuid userid appid; }; + in + { + users = mkMerge ( + foldlAttrs + ( + acc: username: fid: + acc + ++ + foldlAttrs + ( + acc': _: app: + acc' ++ [ { ${getsubname fid app.identity} = getuser fid app.identity; } ] + ) + [ + { + ${getsubname fid 0} = getuser fid 0; + ${username}.extraGroups = [ cfg.sharefs.group ]; + } + ] + cfg.apps + ) + (optional (cfg.sharefs.source != null) { + ${cfg.sharefs.user} = { + uid = lib.mkDefault 1023; + inherit (cfg.sharefs) group; + isSystemUser = true; + home = cfg.sharefs.source; + }; + }) + cfg.users + ); + + groups = mkMerge ( + foldlAttrs + ( + acc: _: fid: + acc + ++ foldlAttrs ( + acc': _: app: + acc' ++ [ { ${getsubname fid app.identity} = getgroup fid app.identity; } ] + ) [ { ${getsubname fid 0} = getgroup fid 0; } ] cfg.apps + ) + (optional (cfg.sharefs.source != null) { + ${cfg.sharefs.group} = { + gid = lib.mkDefault 1023; + }; + }) + cfg.users + ); + }; + }; +} diff --git a/test/hakurei/options.nix b/test/hakurei/options.nix new file mode 100644 index 00000000..f624b6f5 --- /dev/null +++ b/test/hakurei/options.nix @@ -0,0 +1,364 @@ +packages: +{ + lib, + pkgs, + config, + ... +}: + +let + inherit (lib) types mkOption mkEnableOption; + + cfg = config.environment.hakurei; +in + +{ + options = { + environment.hakurei = { + enable = mkEnableOption "hakurei"; + + package = mkOption { + type = types.package; + default = packages.${pkgs.stdenv.hostPlatform.system}.hakurei; + description = "The hakurei package to use."; + }; + + hsuPackage = mkOption { + type = types.package; + default = packages.${pkgs.stdenv.hostPlatform.system}.hsu; + description = "The hsu package to use."; + }; + + users = mkOption { + type = + let + inherit (types) attrsOf ints; + in + attrsOf (ints.between 0 99); + description = '' + Users allowed to spawn hakurei apps and their corresponding hakurei identity. + ''; + }; + + extraHomeConfig = mkOption { + type = types.anything; + description = '' + Extra home-manager configuration to merge with all target users. + ''; + }; + + sharefs = { + package = mkOption { + type = types.package; + default = pkgs.linkFarm "sharefs" { + "bin/sharefs" = "${cfg.package}/libexec/sharefs"; + "bin/mount.fuse.sharefs" = "${cfg.package}/libexec/sharefs"; + }; + description = "The sharefs package to use."; + }; + + user = mkOption { + type = types.str; + default = "sharefs"; + description = '' + Name of the user to run the sharefs daemon as. + ''; + }; + + group = mkOption { + type = types.str; + default = "sharefs"; + description = '' + Name of the group to run the sharefs daemon as. + ''; + }; + + name = mkOption { + type = types.str; + default = "/sdcard"; + description = '' + Host path to mount sharefs on. + ''; + }; + + source = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + Writable backing directory. Setting this to null disables sharefs. + ''; + }; + }; + + apps = mkOption { + type = + let + inherit (types) + int + ints + str + bool + enum + package + anything + submodule + listOf + attrsOf + nullOr + functionTo + ; + in + attrsOf (submodule { + options = { + name = mkOption { + type = str; + description = '' + Name of the app's launcher script. + ''; + }; + + verbose = mkEnableOption "launchers with verbose output"; + + identity = mkOption { + type = ints.between 1 9999; + description = '' + Application identity. Identity 0 is reserved for system services. + ''; + }; + shareUid = mkEnableOption "sharing identity with another application"; + + packages = mkOption { + type = listOf package; + default = [ ]; + description = '' + List of extra packages to install via home-manager. + ''; + }; + + extraConfig = mkOption { + type = anything; + default = { }; + description = '' + Extra home-manager configuration. + ''; + }; + + path = mkOption { + type = nullOr str; + default = null; + description = '' + Custom executable path. + Setting this to null will default to the start script. + ''; + }; + + args = mkOption { + type = nullOr (listOf str); + default = null; + description = '' + Custom args. + Setting this to null will default to script name. + ''; + }; + + script = mkOption { + type = nullOr str; + default = null; + description = '' + Application launch script. + ''; + }; + + command = mkOption { + type = nullOr str; + default = null; + description = '' + Command to run as the target user. + Setting this to null will default command to launcher name. + Has no effect when script is set. + ''; + }; + + groups = mkOption { + type = listOf str; + default = [ ]; + description = '' + List of groups to inherit from the privileged user. + ''; + }; + + shareRuntime = mkEnableOption "sharing of XDG_RUNTIME_DIR between containers under the same identity"; + shareTmpdir = mkEnableOption "sharing of TMPDIR between containers under the same identity"; + + dbus = { + session = mkOption { + type = nullOr (functionTo anything); + default = null; + description = '' + D-Bus session bus custom configuration. + Setting this to null will enable built-in defaults. + ''; + }; + + system = mkOption { + type = nullOr anything; + default = null; + description = '' + D-Bus system bus custom configuration. + Setting this to null will disable the system bus proxy. + ''; + }; + }; + + env = mkOption { + type = nullOr (attrsOf str); + default = null; + description = '' + Environment variables to set for the initial process in the sandbox. + ''; + }; + + wait_delay = mkOption { + type = nullOr int; + default = null; + description = '' + Duration to wait for after interrupting a container's initial process in nanoseconds. + A negative value causes the container to be terminated immediately on cancellation. + Setting this to null defaults to five seconds. + ''; + }; + + devel = mkEnableOption "debugging-related kernel interfaces"; + userns = mkEnableOption "user namespace creation"; + tty = mkEnableOption "access to the controlling terminal"; + multiarch = mkEnableOption "multiarch kernel-level support"; + + hostNet = mkEnableOption "share host net namespace" // { + default = true; + }; + hostAbstract = mkEnableOption "share abstract unix socket scope"; + + schedPolicy = mkOption { + type = nullOr (enum [ + "fifo" + "rr" + "batch" + "idle" + "deadline" + "ext" + ]); + default = null; + description = '' + Scheduling policy to set for the container. + The zero value retains the current scheduling policy. + ''; + }; + schedPriority = mkOption { + type = nullOr (ints.between 1 99); + default = null; + description = '' + Scheduling priority to set for the container. + ''; + }; + + nix = mkEnableOption "nix daemon access"; + mapRealUid = mkEnableOption "mapping to priv-user uid"; + device = mkEnableOption "access to all devices"; + insecureWayland = mkEnableOption "direct access to the Wayland socket"; + + gpu = mkOption { + type = nullOr bool; + default = null; + description = '' + Target process GPU and driver access. + Setting this to null will enable GPU whenever X or Wayland is enabled. + ''; + }; + + useCommonPaths = mkEnableOption "common extra paths" // { + default = true; + }; + + extraPaths = mkOption { + type = listOf (attrsOf anything); + default = [ ]; + description = '' + Extra paths to make available to the container. + ''; + }; + + enablements = { + wayland = mkOption { + type = nullOr bool; + default = true; + description = '' + Whether to share the Wayland server via security-context-v1. + ''; + }; + + x11 = mkOption { + type = nullOr bool; + default = false; + description = '' + Whether to share the X11 socket and allow connection. + ''; + }; + + dbus = mkOption { + type = nullOr bool; + default = true; + description = '' + Whether to proxy D-Bus. + ''; + }; + + pipewire = mkOption { + type = nullOr bool; + default = true; + description = '' + Whether to share the PipeWire server via pipewire-pulse on a SecurityContext socket. + ''; + }; + }; + + share = mkOption { + type = nullOr package; + default = null; + description = '' + Package containing share files. + Setting this to null will default package name to wrapper name. + ''; + }; + }; + }); + default = { }; + description = '' + Declaratively configured hakurei apps. + ''; + }; + + commonPaths = mkOption { + type = types.listOf (types.attrsOf types.anything); + default = [ ]; + description = '' + Common extra paths to make available to the container. + ''; + }; + + shell = mkOption { + type = types.str; + default = "/run/current-system/sw/bin/bash"; + description = '' + Absolute path to preferred shell. + ''; + }; + + stateDir = mkOption { + type = types.str; + description = '' + The state directory where app home directories are stored. + ''; + }; + }; + }; +} diff --git a/test/hakurei/package.nix b/test/hakurei/package.nix new file mode 100644 index 00000000..0facf291 --- /dev/null +++ b/test/hakurei/package.nix @@ -0,0 +1,144 @@ +{ + lib, + stdenv, + buildGo127Module, + makeBinaryWrapper, + xdg-dbus-proxy, + pkg-config, + libffi, + libseccomp, + acl, + wayland, + wayland-protocols, + wayland-scanner, + + libxcb, + libxau, + libxdmcp, + + # for sharefs + fuse3, + + # for passthru.buildInputs + go_1_27, + clang, + xorgproto, + + # for check + util-linux, + nettools, + + glibc, # for ldd + withStatic ? stdenv.hostPlatform.isStatic, +}: + +buildGo127Module rec { + pname = "hakurei"; + version = with lib.strings; removePrefix "v" (trim (builtins.readFile ../../cmd/dist/VERSION)); + + srcFiltered = builtins.path { + name = "${pname}-src"; + path = lib.cleanSource ../../.; + filter = path: type: !(type == "regular" && (lib.hasSuffix ".nix" path || lib.hasSuffix ".py" path)) && !(type == "directory" && lib.hasSuffix "/test" path) && !(type == "directory" && lib.hasSuffix "/cmd/hsu" path); + }; + vendorHash = null; + + src = stdenv.mkDerivation { + name = "${pname}-src-full"; + inherit version; + enableParallelBuilding = true; + src = srcFiltered; + + buildInputs = [ + wayland + wayland-protocols + ]; + + nativeBuildInputs = [ + go_1_27 + pkg-config + wayland-scanner + ]; + + buildPhase = "GOCACHE=$(mktemp -d) go generate ./..."; + installPhase = "cp -r . $out"; + }; + + ldflags = + lib.attrsets.foldlAttrs + ( + ldflags: name: value: + ldflags ++ [ "-X hakurei.app/internal/info.${name}=${value}" ] + ) + ( + [ "-s -w" ] + ++ lib.optionals withStatic [ + "-linkmode external" + "-extldflags \"-static\"" + ] + ) + { + buildVersion = "v${version}"; + hakureiPath = "${placeholder "out"}/libexec/hakurei"; + hsuPath = "/run/wrappers/bin/hsu"; + }; + + env = { + # use clang instead of gcc + CC = "clang -O3 -Werror"; + }; + + buildInputs = [ + libffi + libseccomp + fuse3 + acl + wayland + + libxcb + libxau + libxdmcp + ]; + + nativeBuildInputs = [ + pkg-config + makeBinaryWrapper + + # for container example + nettools + ]; + + postInstall = + let + appPackages = [ + glibc + xdg-dbus-proxy + ]; + in + '' + install -D --target-directory=$out/share/zsh/site-functions cmd/dist/comp/* + + mkdir "$out/libexec" + mv "$out"/bin/* "$out/libexec/" + + makeBinaryWrapper "$out/libexec/hakurei" "$out/bin/hakurei" \ + --inherit-argv0 --prefix PATH : ${lib.makeBinPath appPackages} + ''; + + passthru = { + go = go_1_27; + + targetPkgs = [ + go_1_27 + clang + xorgproto + util-linux + + # for go generate + wayland-protocols + wayland-scanner + ] + ++ buildInputs + ++ nativeBuildInputs; + }; +} diff --git a/test/hakurei/test.py b/test/hakurei/test.py new file mode 100644 index 00000000..98f08275 --- /dev/null +++ b/test/hakurei/test.py @@ -0,0 +1,315 @@ +import json +import shlex + +q = shlex.quote +NODE_GROUPS = ["nodes", "floating_nodes"] + + +def swaymsg(command: str = "", succeed=True, type="command"): + assert command != "" or type != "command", "Must specify command or type" + shell = q(f"swaymsg -t {q(type)} -- {q(command)}") + with machine.nested(f"sending swaymsg {shell!r}" + " (allowed to fail)" * (not succeed)): + ret = (machine.succeed if succeed else machine.execute)( + f"su - alice -c {shell}" + ) + + # execute also returns a status code, but disregard. + if not succeed: + _, ret = ret + + if not succeed and not ret: + return None + + parsed = json.loads(ret) + return parsed + + +def walk(tree): + yield tree + for group in NODE_GROUPS: + for node in tree.get(group, []): + yield from walk(node) + + +def wait_for_window(pattern): + def func(last_chance): + nodes = (node["name"] for node in walk(swaymsg(type="get_tree"))) + + if last_chance: + nodes = list(nodes) + machine.log(f"Last call! Current list of windows: {nodes}") + + return any(pattern in name for name in nodes) + + retry(func) + + +def collect_state_ui(name): + swaymsg(f"exec hakurei ps > '/tmp/{name}.ps'") + machine.wait_for_file(f"/tmp/{name}.ps") + machine.copy_from_vm(f"/tmp/{name}.ps", "") + swaymsg(f"exec hakurei --json ps > '/tmp/{name}.json'") + machine.wait_for_file(f"/tmp/{name}.json") + machine.copy_from_vm(f"/tmp/{name}.json", "") + machine.screenshot(name) + + +def check_state(name, enablements): + instances = json.loads(machine.succeed("sudo -u alice -i XDG_RUNTIME_DIR=/run/user/1000 hakurei --json ps")) + if len(instances) != 1: + raise Exception(f"unexpected state length {len(instances)}") + instance = instances[0] + + command = f"{name}-start" + if not (instance['container']['path'].startswith("/nix/store/")) or not (instance['container']['path'].endswith(command)): + raise Exception(f"unexpected path {instance['path']}") + + if len(instance['container']['args']) != 1 or instance['container']['args'][0] != command: + raise Exception(f"unexpected args {instance['args']}") + + if instance['enablements'] != enablements: + raise Exception(f"unexpected enablements {instance['enablements']['enablements']}") + + +def hakurei(command): + swaymsg(f"exec hakurei {command}") + + +start_all() +machine.wait_for_unit("multi-user.target") + +# To check hakurei's version: +print(machine.succeed("sudo -u alice -i hakurei version")) + +# Wait for Sway to complete startup: +machine.wait_for_file("/run/user/1000/wayland-1") +machine.wait_for_file("/tmp/sway-ipc.sock") + +# Run hakurei Go tests outside of nix build in the background: +swaymsg("exec hakurei-test") + +# Deny unmapped uid: +denyOutput = machine.fail("sudo -u untrusted -i hakurei exec &>/dev/stdout") +print(denyOutput) +denyOutputVerbose = machine.fail("sudo -u untrusted -i hakurei -v exec &>/dev/stdout") +print(denyOutputVerbose) + +# Direct hsu call: +userid = machine.succeed("sudo -u alice -i hsu") +if userid != "0": + raise Exception(f"unexpected userid: {userid}") + +# Verify hsu fault behaviour: +if denyOutput != "hsu: uid 1001 is not in the hsurc file\n": + raise Exception(f"unexpected deny output:\n{denyOutput}") +if denyOutputVerbose != "hsu: uid 1001 is not in the hsurc file\nhakurei: *cannot retrieve user id from setuid wrapper: current user is not in the hsurc file\n": + raise Exception(f"unexpected deny verbose output:\n{denyOutputVerbose}") + +# Verify timeout behaviour: +machine.succeed('sudo -u alice -i hakurei-check-linger-timeout > /var/tmp/linger-stdout 2> /var/tmp/linger-stderr || (cat /var/tmp/linger-stderr; false)') +linger_stdout = machine.succeed("cat /var/tmp/linger-stdout") +linger_stderr = machine.succeed("cat /var/tmp/linger-stderr") +if linger_stdout != "": + raise Exception(f"unexpected stdout: {linger_stdout}") +if linger_stderr != "init: timeout exceeded waiting for lingering processes\n": + raise Exception(f"unexpected stderr: {linger_stderr}") + +check_offset = 0 + + +def hakurei_identity(offset): + return 1+check_offset+offset + + +# Start hakurei permissive defaults outside Wayland session: +print(machine.succeed("sudo -u alice -i hakurei -v exec -a 0 touch /tmp/pd-bare-ok")) +machine.wait_for_file("/tmp/hakurei.0/tmpdir/0/pd-bare-ok") + +# Verify silent output permissive defaults: +output = machine.succeed("sudo -u alice -i hakurei exec -a 0 true &>/dev/stdout") +if output != "": + raise Exception(f"unexpected output\n{output}") + +# Verify silent output permissive defaults signal: +def silent_output_interrupt(flags): + swaymsg("exec foot") + wait_for_window("alice@machine") + # identity 0 does not have home-manager + machine.send_chars(f"exec hakurei exec {flags}-a 0 sh -c 'export PATH=/run/current-system/sw/bin:$PATH && touch /tmp/pd-silent-ready && sleep infinity' &>/tmp/pd-silent\n") + machine.wait_for_file("/tmp/hakurei.0/tmpdir/0/pd-silent-ready") + machine.succeed("rm /tmp/hakurei.0/tmpdir/0/pd-silent-ready") + machine.send_key("ctrl-c") + machine.wait_until_fails("pgrep foot") + machine.wait_until_fails(f"pgrep -u alice -f 'hakurei exec {flags}-a 0 '") + output = machine.succeed("cat /tmp/pd-silent && rm /tmp/pd-silent") + if output != "": + raise Exception(f"unexpected output\n{output}") + + +silent_output_interrupt("") +silent_output_interrupt("--dbus ") # this one is especially painful as it maintains a helper +silent_output_interrupt("--wayland -X --dbus --pulse ") + +# Verify graceful failure on bad Wayland display name: +print(machine.fail("sudo -u alice -i hakurei -v exec --wayland true")) + +# Start hakurei permissive defaults within Wayland session: +hakurei('-v exec --wayland --dbus --dbus-log notify-send -a "NixOS Tests" "Test notification" "Notification from within sandbox." && touch /tmp/dbus-ok') +machine.wait_for_file("/tmp/dbus-ok") +collect_state_ui("dbus_notify_exited") +# not in pid namespace, verify termination +machine.wait_until_fails("pgrep xdg-dbus-proxy") +machine.succeed("pkill -9 mako") + +# Check revert type selection: +hakurei("-v exec --wayland -X --dbus --pulse -u p0 foot && touch /tmp/p0-exit-ok") +wait_for_window("p0@machine") +print(machine.succeed("getfacl --absolute-names --omit-header --numeric /tmp/hakurei.0/runtime | grep 10000")) +hakurei("-v exec --wayland -X --dbus --pulse -u p1 foot && touch /tmp/p1-exit-ok") +wait_for_window("p1@machine") +print(machine.succeed("getfacl --absolute-names --omit-header --numeric /tmp/hakurei.0/runtime | grep 10000")) +machine.send_chars("exit\n") +machine.wait_for_file("/tmp/p1-exit-ok") +# Verify acl is kept alive: +print(machine.succeed("getfacl --absolute-names --omit-header --numeric /tmp/hakurei.0/runtime | grep 10000")) +machine.send_chars("exit\n") +machine.wait_for_file("/tmp/p0-exit-ok") +machine.fail("getfacl --absolute-names --omit-header --numeric /tmp/hakurei.0/runtime | grep 10000") + +# Check invalid identifier fd behaviour: +machine.fail('echo \'{"container":{"shell":"/proc/nonexistent","home":"/proc/nonexistent","path":"/proc/nonexistent"}}\' | sudo -u alice -i hakurei -v run --identifier-fd 32767 - 2>&1 | tee > /tmp/invalid-identifier-fd') +machine.wait_for_file("/tmp/invalid-identifier-fd") +print(machine.succeed('grep "^hakurei: cannot write identifier: bad file descriptor$" /tmp/invalid-identifier-fd')) + +# Check interrupt shim behaviour: +swaymsg("exec sh -c 'ne-foot; echo -n $? > /tmp/monitor-exit-code'") +wait_for_window(f"u0_a{hakurei_identity(0)}@machine") +machine.succeed("pkill -INT -f 'hakurei -v run '") +machine.wait_until_fails("pgrep foot") +machine.wait_for_file("/tmp/monitor-exit-code") +interrupt_exit_code = int(machine.succeed("cat /tmp/monitor-exit-code")) +if interrupt_exit_code != 230: + raise Exception(f"unexpected exit code {interrupt_exit_code}") + +# Check interrupt shim behaviour immediate termination: +swaymsg("exec sh -c 'ne-foot-immediate; echo -n $? > /tmp/monitor-exit-code'") +wait_for_window(f"u0_a{hakurei_identity(0)}@machine") +machine.succeed("pkill -INT -f 'hakurei -v run '") +machine.wait_until_fails("pgrep foot") +machine.wait_for_file("/tmp/monitor-exit-code") +interrupt_exit_code = int(machine.succeed("cat /tmp/monitor-exit-code")) +if interrupt_exit_code != 254: + raise Exception(f"unexpected exit code {interrupt_exit_code}") + +# Check shim SIGCONT from unexpected process behaviour: +swaymsg("exec sh -c 'ne-foot &> /tmp/shim-cont-unexpected-pid'") +wait_for_window(f"u0_a{hakurei_identity(0)}@machine") +machine.succeed("pkill -CONT -f 'hakurei shim'") +machine.succeed("pkill -INT -f 'hakurei -v run '") +machine.wait_until_fails("pgrep foot") +machine.wait_for_file("/tmp/shim-cont-unexpected-pid") +print(machine.succeed('grep "shim: got SIGCONT from unexpected process$" /tmp/shim-cont-unexpected-pid')) + +# Check setscheduler: +sched_unset = int(machine.succeed("sudo -u alice -i hakurei -v exec cat /proc/self/sched | grep '^policy' | tr -d ' ' | cut -d ':' -f 2")) +if sched_unset != 0: + raise Exception(f"unexpected unset policy: {sched_unset}") +sched_idle = int(machine.succeed("sudo -u alice -i hakurei -v exec --policy=idle cat /proc/self/sched | grep '^policy' | tr -d ' ' | cut -d ':' -f 2")) +if sched_idle != 5: + raise Exception(f"unexpected idle policy: {sched_idle}") +sched_rr = int(machine.succeed("sudo -u alice -i hakurei -v exec --policy=rr cat /proc/self/sched | grep '^policy' | tr -d ' ' | cut -d ':' -f 2")) +if sched_rr != 2: + raise Exception(f"unexpected round-robin policy: {sched_idle}") + +# Start app (foot) with Wayland enablement: +swaymsg("exec ne-foot") +wait_for_window(f"u0_a{hakurei_identity(0)}@machine") +machine.send_chars("clear; wayland-info && touch /var/tmp/client-ok\n") +machine.wait_for_file("/var/tmp/client-ok") +collect_state_ui("foot_wayland") +check_state("ne-foot", {"wayland": True}) +# Verify lack of acl on XDG_RUNTIME_DIR: +machine.fail(f"getfacl --absolute-names --omit-header --numeric /run/user/1000 | grep {hakurei_identity(0) + 10000}") +machine.send_chars("exit\n") +machine.wait_until_fails("pgrep foot") +machine.fail(f"getfacl --absolute-names --omit-header --numeric /run/user/1000 | grep {hakurei_identity(0) + 10000}") + +# Test pipewire-pulse: +swaymsg("exec pa-foot") +wait_for_window(f"u0_a{hakurei_identity(1)}@machine") +machine.send_chars("clear; pactl info && touch /var/tmp/pulse-ok\n") +machine.wait_for_file("/var/tmp/pulse-ok") +collect_state_ui("pulse_wayland") +check_state("pa-foot", {"wayland": True, "pipewire": True}) +machine.fail("find /tmp -maxdepth 1 -type d -name '.hakurei-shim-*' -print -exec false '{}' +") +machine.send_chars("exit\n") +machine.wait_until_fails("pgrep foot") +machine.wait_until_fails("pgrep -x hakurei") +machine.succeed("find /tmp -maxdepth 1 -type d -name '.hakurei-shim-*' -print -exec false '{}' +") +# Test PipeWire SecurityContext: +machine.succeed("sudo -u alice -i XDG_RUNTIME_DIR=/run/user/1000 hakurei -v exec --pulse pactl info") +machine.fail("sudo -u alice -i XDG_RUNTIME_DIR=/run/user/1000 hakurei -v exec --pulse pactl set-sink-mute @DEFAULT_SINK@ toggle") +# Test PipeWire direct access: +machine.succeed("sudo -u alice -i XDG_RUNTIME_DIR=/run/user/1000 pw-dump") +machine.fail("sudo -u alice -i XDG_RUNTIME_DIR=/run/user/1000 hakurei -v exec --pipewire pw-dump") + +# Test XWayland (foot does not support X): +swaymsg("exec x11-alacritty") +wait_for_window(f"u0_a{hakurei_identity(0)}@machine") +machine.send_chars("clear; glinfo && touch /var/tmp/x11-ok\n") +machine.wait_for_file("/var/tmp/x11-ok") +collect_state_ui("alacritty_x11") +check_state("x11-alacritty", {"x11": True}) +machine.send_chars("exit\n") +machine.wait_until_fails("pgrep alacritty") + +# Start app (foot) with direct Wayland access: +swaymsg("exec da-foot") +wait_for_window(f"u0_a{hakurei_identity(3)}@machine") +machine.send_chars("clear; wayland-info && touch /var/tmp/direct-ok\n") +collect_state_ui("foot_direct") +machine.wait_for_file("/var/tmp/direct-ok") +check_state("da-foot", {"wayland": True}) +# Verify acl on XDG_RUNTIME_DIR: +print(machine.succeed(f"getfacl --absolute-names --omit-header --numeric /run/user/1000 | grep {hakurei_identity(3) + 10000}")) +machine.send_chars("exit\n") +machine.wait_until_fails("pgrep foot") +# Verify acl cleanup on XDG_RUNTIME_DIR: +machine.wait_until_fails(f"getfacl --absolute-names --omit-header --numeric /run/user/1000 | grep {hakurei_identity(3) + 10000}") + +# Test syscall filter: +print(machine.fail("sudo -u alice -i XDG_RUNTIME_DIR=/run/user/1000 strace-failure")) + +# Start app (foot) with Wayland enablement from a terminal: +swaymsg("exec foot $SHELL -c '(ne-foot) & disown && exec $SHELL'") +wait_for_window(f"u0_a{hakurei_identity(0)}@machine") +machine.send_chars("clear; wayland-info && touch /var/tmp/term-ok\n") +machine.wait_for_file("/var/tmp/term-ok") +machine.send_key("alt-h") +machine.send_chars("clear; hakurei show $(hakurei ps --short) && touch /tmp/ps-show-ok && exec cat\n") +machine.wait_for_file("/tmp/ps-show-ok") +collect_state_ui("foot_wayland_term") +check_state("ne-foot", {"wayland": True}) +machine.send_key("alt-l") +machine.send_chars("exit\n") +wait_for_window("alice@machine") +machine.send_key("ctrl-c") +machine.wait_until_fails("pgrep foot") + +# Exit Sway and verify process exit status 0: +machine.wait_until_fails("pgrep -x hakurei") +swaymsg("exit", succeed=False) +machine.wait_for_file("/tmp/sway-exit-ok") + +# Print hakurei share and rundir contents: +print(machine.succeed("find /tmp/hakurei.0 " + + "-path '/tmp/hakurei.0/runtime/*/*' -prune -o " + + "-path '/tmp/hakurei.0/tmpdir/*/*' -prune -o " + + "-print")) +print(machine.succeed("find /run/user/1000/hakurei")) +machine.succeed("find /tmp -maxdepth 1 -type d -name '.hakurei-shim-*' -print -exec false '{}' +") + +# Verify go test status: +machine.wait_for_file("/tmp/hakurei-test-done") +print(machine.succeed("cat /tmp/hakurei-test.log")) +machine.wait_for_file("/tmp/hakurei-test-ok") diff --git a/test/hsu.nix b/test/hsu.nix deleted file mode 100644 index 7acb3529..00000000 --- a/test/hsu.nix +++ /dev/null @@ -1,23 +0,0 @@ -{ - lib, - buildGoModule, - hakurei ? abort "hakurei package required", -}: - -buildGoModule { - pname = "${hakurei.pname}-hsu"; - inherit (hakurei) version; - - src = ../cmd/hsu; - inherit (hakurei) vendorHash; - env.CGO_ENABLED = 0; - - preBuild = '' - go mod init hsu >& /dev/null - ''; - - ldflags = lib.attrsets.foldlAttrs ( - ldflags: name: value: - ldflags ++ [ "-X main.${name}=${value}" ] - ) [ "-s -w" ] { hakureiPath = "${hakurei}/libexec/hakurei"; }; -} diff --git a/test/nixos.nix b/test/nixos.nix deleted file mode 100644 index 49bfffb6..00000000 --- a/test/nixos.nix +++ /dev/null @@ -1,407 +0,0 @@ -packages: -{ - lib, - pkgs, - config, - ... -}: - -let - inherit (lib) - lists - attrsets - mkMerge - mkIf - mapAttrs - foldlAttrs - optional - optionals - ; - - cfg = config.environment.hakurei; - - # userid*userOffset + appStart + appid - getsubuid = userid: appid: userid * 100000 + 10000 + appid; - getsubname = userid: appid: "u${toString userid}_a${toString appid}"; - getsubhome = userid: appid: "${cfg.stateDir}/u${toString userid}/a${toString appid}"; - - mountpoints = { - ${cfg.sharefs.name} = mkIf (cfg.sharefs.source != null) { - depends = [ cfg.sharefs.source ]; - device = "sharefs"; - fsType = "fuse.sharefs"; - noCheck = true; - options = [ - "rw" - "noexec" - "nosuid" - "nodev" - "noatime" - "allow_other" - "mkdir" - "source=${cfg.sharefs.source}" - "setuid=${toString config.users.users.${cfg.sharefs.user}.uid}" - "setgid=${toString config.users.groups.${cfg.sharefs.group}.gid}" - ]; - }; - }; -in - -{ - imports = [ (import ./options.nix packages) ]; - - options = { - # Forward declare a dummy option for VM filesystems since the real one won't exist - # unless the VM module is actually imported. - virtualisation.fileSystems = lib.mkOption { }; - }; - - config = mkIf cfg.enable { - assertions = [ - ( - let - conflictingApps = foldlAttrs ( - acc: id: app: - ( - acc - ++ foldlAttrs ( - acc': id': app': - if id == id' || app.shareUid && app'.shareUid || app.identity != app'.identity then acc' else acc' ++ [ id ] - ) [ ] cfg.apps - ) - ) [ ] cfg.apps; - in - { - assertion = (lists.length conflictingApps) == 0; - message = "the following hakurei apps have conflicting identities: " + (builtins.concatStringsSep ", " conflictingApps); - } - ) - ]; - - security.wrappers.hsu = { - source = "${cfg.hsuPackage}/bin/hsu"; - setuid = true; - owner = "root"; - group = "root"; - }; - - environment.etc.hsurc = { - mode = "0400"; - text = foldlAttrs ( - acc: username: fid: - "${toString config.users.users.${username}.uid} ${toString fid}\n" + acc - ) "" cfg.users; - }; - - environment.systemPackages = optional (cfg.sharefs.source != null) cfg.sharefs.package; - fileSystems = mountpoints; - virtualisation.fileSystems = mountpoints; - - home-manager = - let - privPackages = mapAttrs (_: userid: { - home.packages = foldlAttrs ( - acc: id: app: - [ - ( - let - extendDBusDefault = id: ext: { - filter = true; - - talk = [ "org.freedesktop.Notifications" ] ++ ext.talk; - own = [ - "${id}.*" - "org.mpris.MediaPlayer2.${id}.*" - ] - ++ ext.own; - - inherit (ext) call broadcast; - }; - dbusConfig = - let - default = { - talk = [ ]; - own = [ ]; - call = { }; - broadcast = { }; - }; - in - { - session_bus = if app.dbus.session != null then (app.dbus.session (extendDBusDefault id)) else (extendDBusDefault id default); - system_bus = app.dbus.system; - }; - command = if app.command == null then app.name else app.command; - script = if app.script == null then ("exec " + command + " $@") else app.script; - isGraphical = if app.gpu != null then app.gpu else app.enablements.wayland || app.enablements.x11; - - conf = { - inherit id; - inherit (app) identity enablements; - inherit (dbusConfig) session_bus system_bus; - direct_wayland = app.insecureWayland; - sched_policy = app.schedPolicy; - sched_priority = app.schedPriority; - groups = app.groups ++ optional (cfg.sharefs.source != null) cfg.sharefs.group; - - container = { - inherit (app) - wait_delay - devel - userns - device - tty - multiarch - env - ; - map_real_uid = app.mapRealUid; - host_net = app.hostNet; - host_abstract = app.hostAbstract; - share_runtime = app.shareRuntime; - share_tmpdir = app.shareTmpdir; - - filesystem = - let - bind = src: { - type = "bind"; - inherit src; - }; - optBind = src: { - type = "bind"; - inherit src; - optional = true; - }; - optDevBind = src: { - type = "bind"; - inherit src; - dev = true; - optional = true; - }; - in - [ - (bind "/bin") - (bind "/usr/bin") - (bind "/nix/store") - (optBind "/sys/block") - (optBind "/sys/bus") - (optBind "/sys/class") - (optBind "/sys/dev") - (optBind "/sys/devices") - ] - ++ optionals app.nix [ - (bind "/nix/var") - ] - ++ optionals isGraphical [ - (optDevBind "/dev/dri") - (optDevBind "/dev/nvidiactl") - (optDevBind "/dev/nvidia-modeset") - (optDevBind "/dev/nvidia-uvm") - (optDevBind "/dev/nvidia-uvm-tools") - (optDevBind "/dev/nvidia0") - ] - ++ optionals app.useCommonPaths cfg.commonPaths - ++ app.extraPaths - ++ [ - { - type = "bind"; - dst = "/etc/"; - src = "/etc/"; - special = true; - } - { - type = "link"; - dst = "/run/current-system"; - linkname = "/run/current-system"; - dereference = true; - } - ] - ++ optionals (isGraphical && config.hardware.graphics.enable) ( - [ - { - type = "link"; - dst = "/run/opengl-driver"; - linkname = config.systemd.tmpfiles.settings.graphics-driver."/run/opengl-driver"."L+".argument; - } - ] - ++ optionals (app.multiarch && config.hardware.graphics.enable32Bit) [ - { - type = "link"; - dst = "/run/opengl-driver-32"; - linkname = config.systemd.tmpfiles.settings.graphics-driver."/run/opengl-driver-32"."L+".argument; - } - ] - ) - ++ [ - { - type = "bind"; - src = getsubhome userid app.identity; - write = true; - ensure = true; - } - ]; - - username = getsubname userid app.identity; - inherit (cfg) shell; - home = getsubhome userid app.identity; - - path = - if app.path == null then - pkgs.writeScript "${app.name}-start" '' - #!${pkgs.zsh}${pkgs.zsh.shellPath} - ${script} - '' - else - app.path; - args = if app.args == null then [ "${app.name}-start" ] else app.args; - }; - }; - - checkedConfig = - name: value: - let - file = pkgs.writeText name (builtins.toJSON value); - in - pkgs.runCommand "checked-${name}" { nativeBuildInputs = [ cfg.package ]; } '' - ln -vs ${file} "$out" - hakurei show --no-store ${file} - ''; - in - pkgs.writeShellScriptBin app.name '' - exec hakurei${if app.verbose then " -v" else ""}${if app.insecureWayland then " --insecure" else ""} run ${checkedConfig "hakurei-app-${app.name}.json" conf} $@ - '' - ) - ] - ++ ( - let - pkg = if app.share != null then app.share else pkgs.${app.name}; - copy = source: "[ -d '${source}' ] && cp -Lrv '${source}' $out/share || true"; - in - optional (app.enablements.wayland || app.enablements.x11) ( - pkgs.runCommand "${app.name}-share" { } '' - mkdir -p $out/share - ${copy "${pkg}/share/applications"} - ${copy "${pkg}/share/pixmaps"} - ${copy "${pkg}/share/icons"} - ${copy "${pkg}/share/man"} - - if test -d "$out/share/applications"; then - substituteInPlace $out/share/applications/* \ - --replace-warn '${pkg}/bin/' "" \ - --replace-warn '${pkg}/libexec/' "" - fi - '' - ) - ) - ++ acc - ) [ cfg.package ] cfg.apps; - }) cfg.users; - in - { - useUserPackages = false; # prevent users.users entries from being added - - users = - mkMerge - (foldlAttrs - ( - acc: _: fid: - foldlAttrs - ( - acc: _: app: - ( - let - key = getsubname fid app.identity; - in - { - usernames = acc.usernames // { - ${key} = true; - }; - merge = acc.merge ++ [ - { - ${key} = mkMerge ( - [ - app.extraConfig - { home.packages = app.packages; } - ] - ++ lib.optional (!attrsets.hasAttrByPath [ key ] acc.usernames) cfg.extraHomeConfig - ); - } - ]; - } - ) - ) - { - inherit (acc) usernames; - merge = acc.merge ++ [ { ${getsubname fid 0} = cfg.extraHomeConfig; } ]; - } - cfg.apps - ) - { - usernames = { }; - merge = [ privPackages ]; - } - cfg.users - ).merge; - }; - - users = - let - getuser = userid: appid: { - isSystemUser = true; - createHome = true; - description = "Hakurei subordinate user ${toString appid} (u${toString userid})"; - group = getsubname userid appid; - home = getsubhome userid appid; - uid = getsubuid userid appid; - }; - getgroup = userid: appid: { gid = getsubuid userid appid; }; - in - { - users = mkMerge ( - foldlAttrs - ( - acc: username: fid: - acc - ++ - foldlAttrs - ( - acc': _: app: - acc' ++ [ { ${getsubname fid app.identity} = getuser fid app.identity; } ] - ) - [ - { - ${getsubname fid 0} = getuser fid 0; - ${username}.extraGroups = [ cfg.sharefs.group ]; - } - ] - cfg.apps - ) - (optional (cfg.sharefs.source != null) { - ${cfg.sharefs.user} = { - uid = lib.mkDefault 1023; - inherit (cfg.sharefs) group; - isSystemUser = true; - home = cfg.sharefs.source; - }; - }) - cfg.users - ); - - groups = mkMerge ( - foldlAttrs - ( - acc: _: fid: - acc - ++ foldlAttrs ( - acc': _: app: - acc' ++ [ { ${getsubname fid app.identity} = getgroup fid app.identity; } ] - ) [ { ${getsubname fid 0} = getgroup fid 0; } ] cfg.apps - ) - (optional (cfg.sharefs.source != null) { - ${cfg.sharefs.group} = { - gid = lib.mkDefault 1023; - }; - }) - cfg.users - ); - }; - }; -} diff --git a/test/options.nix b/test/options.nix deleted file mode 100644 index f624b6f5..00000000 --- a/test/options.nix +++ /dev/null @@ -1,364 +0,0 @@ -packages: -{ - lib, - pkgs, - config, - ... -}: - -let - inherit (lib) types mkOption mkEnableOption; - - cfg = config.environment.hakurei; -in - -{ - options = { - environment.hakurei = { - enable = mkEnableOption "hakurei"; - - package = mkOption { - type = types.package; - default = packages.${pkgs.stdenv.hostPlatform.system}.hakurei; - description = "The hakurei package to use."; - }; - - hsuPackage = mkOption { - type = types.package; - default = packages.${pkgs.stdenv.hostPlatform.system}.hsu; - description = "The hsu package to use."; - }; - - users = mkOption { - type = - let - inherit (types) attrsOf ints; - in - attrsOf (ints.between 0 99); - description = '' - Users allowed to spawn hakurei apps and their corresponding hakurei identity. - ''; - }; - - extraHomeConfig = mkOption { - type = types.anything; - description = '' - Extra home-manager configuration to merge with all target users. - ''; - }; - - sharefs = { - package = mkOption { - type = types.package; - default = pkgs.linkFarm "sharefs" { - "bin/sharefs" = "${cfg.package}/libexec/sharefs"; - "bin/mount.fuse.sharefs" = "${cfg.package}/libexec/sharefs"; - }; - description = "The sharefs package to use."; - }; - - user = mkOption { - type = types.str; - default = "sharefs"; - description = '' - Name of the user to run the sharefs daemon as. - ''; - }; - - group = mkOption { - type = types.str; - default = "sharefs"; - description = '' - Name of the group to run the sharefs daemon as. - ''; - }; - - name = mkOption { - type = types.str; - default = "/sdcard"; - description = '' - Host path to mount sharefs on. - ''; - }; - - source = mkOption { - type = types.nullOr types.str; - default = null; - description = '' - Writable backing directory. Setting this to null disables sharefs. - ''; - }; - }; - - apps = mkOption { - type = - let - inherit (types) - int - ints - str - bool - enum - package - anything - submodule - listOf - attrsOf - nullOr - functionTo - ; - in - attrsOf (submodule { - options = { - name = mkOption { - type = str; - description = '' - Name of the app's launcher script. - ''; - }; - - verbose = mkEnableOption "launchers with verbose output"; - - identity = mkOption { - type = ints.between 1 9999; - description = '' - Application identity. Identity 0 is reserved for system services. - ''; - }; - shareUid = mkEnableOption "sharing identity with another application"; - - packages = mkOption { - type = listOf package; - default = [ ]; - description = '' - List of extra packages to install via home-manager. - ''; - }; - - extraConfig = mkOption { - type = anything; - default = { }; - description = '' - Extra home-manager configuration. - ''; - }; - - path = mkOption { - type = nullOr str; - default = null; - description = '' - Custom executable path. - Setting this to null will default to the start script. - ''; - }; - - args = mkOption { - type = nullOr (listOf str); - default = null; - description = '' - Custom args. - Setting this to null will default to script name. - ''; - }; - - script = mkOption { - type = nullOr str; - default = null; - description = '' - Application launch script. - ''; - }; - - command = mkOption { - type = nullOr str; - default = null; - description = '' - Command to run as the target user. - Setting this to null will default command to launcher name. - Has no effect when script is set. - ''; - }; - - groups = mkOption { - type = listOf str; - default = [ ]; - description = '' - List of groups to inherit from the privileged user. - ''; - }; - - shareRuntime = mkEnableOption "sharing of XDG_RUNTIME_DIR between containers under the same identity"; - shareTmpdir = mkEnableOption "sharing of TMPDIR between containers under the same identity"; - - dbus = { - session = mkOption { - type = nullOr (functionTo anything); - default = null; - description = '' - D-Bus session bus custom configuration. - Setting this to null will enable built-in defaults. - ''; - }; - - system = mkOption { - type = nullOr anything; - default = null; - description = '' - D-Bus system bus custom configuration. - Setting this to null will disable the system bus proxy. - ''; - }; - }; - - env = mkOption { - type = nullOr (attrsOf str); - default = null; - description = '' - Environment variables to set for the initial process in the sandbox. - ''; - }; - - wait_delay = mkOption { - type = nullOr int; - default = null; - description = '' - Duration to wait for after interrupting a container's initial process in nanoseconds. - A negative value causes the container to be terminated immediately on cancellation. - Setting this to null defaults to five seconds. - ''; - }; - - devel = mkEnableOption "debugging-related kernel interfaces"; - userns = mkEnableOption "user namespace creation"; - tty = mkEnableOption "access to the controlling terminal"; - multiarch = mkEnableOption "multiarch kernel-level support"; - - hostNet = mkEnableOption "share host net namespace" // { - default = true; - }; - hostAbstract = mkEnableOption "share abstract unix socket scope"; - - schedPolicy = mkOption { - type = nullOr (enum [ - "fifo" - "rr" - "batch" - "idle" - "deadline" - "ext" - ]); - default = null; - description = '' - Scheduling policy to set for the container. - The zero value retains the current scheduling policy. - ''; - }; - schedPriority = mkOption { - type = nullOr (ints.between 1 99); - default = null; - description = '' - Scheduling priority to set for the container. - ''; - }; - - nix = mkEnableOption "nix daemon access"; - mapRealUid = mkEnableOption "mapping to priv-user uid"; - device = mkEnableOption "access to all devices"; - insecureWayland = mkEnableOption "direct access to the Wayland socket"; - - gpu = mkOption { - type = nullOr bool; - default = null; - description = '' - Target process GPU and driver access. - Setting this to null will enable GPU whenever X or Wayland is enabled. - ''; - }; - - useCommonPaths = mkEnableOption "common extra paths" // { - default = true; - }; - - extraPaths = mkOption { - type = listOf (attrsOf anything); - default = [ ]; - description = '' - Extra paths to make available to the container. - ''; - }; - - enablements = { - wayland = mkOption { - type = nullOr bool; - default = true; - description = '' - Whether to share the Wayland server via security-context-v1. - ''; - }; - - x11 = mkOption { - type = nullOr bool; - default = false; - description = '' - Whether to share the X11 socket and allow connection. - ''; - }; - - dbus = mkOption { - type = nullOr bool; - default = true; - description = '' - Whether to proxy D-Bus. - ''; - }; - - pipewire = mkOption { - type = nullOr bool; - default = true; - description = '' - Whether to share the PipeWire server via pipewire-pulse on a SecurityContext socket. - ''; - }; - }; - - share = mkOption { - type = nullOr package; - default = null; - description = '' - Package containing share files. - Setting this to null will default package name to wrapper name. - ''; - }; - }; - }); - default = { }; - description = '' - Declaratively configured hakurei apps. - ''; - }; - - commonPaths = mkOption { - type = types.listOf (types.attrsOf types.anything); - default = [ ]; - description = '' - Common extra paths to make available to the container. - ''; - }; - - shell = mkOption { - type = types.str; - default = "/run/current-system/sw/bin/bash"; - description = '' - Absolute path to preferred shell. - ''; - }; - - stateDir = mkOption { - type = types.str; - description = '' - The state directory where app home directories are stored. - ''; - }; - }; - }; -} diff --git a/test/package.nix b/test/package.nix deleted file mode 100644 index e68420e3..00000000 --- a/test/package.nix +++ /dev/null @@ -1,144 +0,0 @@ -{ - lib, - stdenv, - buildGo127Module, - makeBinaryWrapper, - xdg-dbus-proxy, - pkg-config, - libffi, - libseccomp, - acl, - wayland, - wayland-protocols, - wayland-scanner, - - libxcb, - libxau, - libxdmcp, - - # for sharefs - fuse3, - - # for passthru.buildInputs - go_1_27, - clang, - xorgproto, - - # for check - util-linux, - nettools, - - glibc, # for ldd - withStatic ? stdenv.hostPlatform.isStatic, -}: - -buildGo127Module rec { - pname = "hakurei"; - version = with lib.strings; removePrefix "v" (trim (builtins.readFile ../cmd/dist/VERSION)); - - srcFiltered = builtins.path { - name = "${pname}-src"; - path = lib.cleanSource ../.; - filter = path: type: !(type == "regular" && (lib.hasSuffix ".nix" path || lib.hasSuffix ".py" path)) && !(type == "directory" && lib.hasSuffix "/test" path) && !(type == "directory" && lib.hasSuffix "/cmd/hsu" path); - }; - vendorHash = null; - - src = stdenv.mkDerivation { - name = "${pname}-src-full"; - inherit version; - enableParallelBuilding = true; - src = srcFiltered; - - buildInputs = [ - wayland - wayland-protocols - ]; - - nativeBuildInputs = [ - go_1_27 - pkg-config - wayland-scanner - ]; - - buildPhase = "GOCACHE=$(mktemp -d) go generate ./..."; - installPhase = "cp -r . $out"; - }; - - ldflags = - lib.attrsets.foldlAttrs - ( - ldflags: name: value: - ldflags ++ [ "-X hakurei.app/internal/info.${name}=${value}" ] - ) - ( - [ "-s -w" ] - ++ lib.optionals withStatic [ - "-linkmode external" - "-extldflags \"-static\"" - ] - ) - { - buildVersion = "v${version}"; - hakureiPath = "${placeholder "out"}/libexec/hakurei"; - hsuPath = "/run/wrappers/bin/hsu"; - }; - - env = { - # use clang instead of gcc - CC = "clang -O3 -Werror"; - }; - - buildInputs = [ - libffi - libseccomp - fuse3 - acl - wayland - - libxcb - libxau - libxdmcp - ]; - - nativeBuildInputs = [ - pkg-config - makeBinaryWrapper - - # for container example - nettools - ]; - - postInstall = - let - appPackages = [ - glibc - xdg-dbus-proxy - ]; - in - '' - install -D --target-directory=$out/share/zsh/site-functions cmd/dist/comp/* - - mkdir "$out/libexec" - mv "$out"/bin/* "$out/libexec/" - - makeBinaryWrapper "$out/libexec/hakurei" "$out/bin/hakurei" \ - --inherit-argv0 --prefix PATH : ${lib.makeBinPath appPackages} - ''; - - passthru = { - go = go_1_27; - - targetPkgs = [ - go_1_27 - clang - xorgproto - util-linux - - # for go generate - wayland-protocols - wayland-scanner - ] - ++ buildInputs - ++ nativeBuildInputs; - }; -} diff --git a/test/test.py b/test/test.py deleted file mode 100644 index 98f08275..00000000 --- a/test/test.py +++ /dev/null @@ -1,315 +0,0 @@ -import json -import shlex - -q = shlex.quote -NODE_GROUPS = ["nodes", "floating_nodes"] - - -def swaymsg(command: str = "", succeed=True, type="command"): - assert command != "" or type != "command", "Must specify command or type" - shell = q(f"swaymsg -t {q(type)} -- {q(command)}") - with machine.nested(f"sending swaymsg {shell!r}" + " (allowed to fail)" * (not succeed)): - ret = (machine.succeed if succeed else machine.execute)( - f"su - alice -c {shell}" - ) - - # execute also returns a status code, but disregard. - if not succeed: - _, ret = ret - - if not succeed and not ret: - return None - - parsed = json.loads(ret) - return parsed - - -def walk(tree): - yield tree - for group in NODE_GROUPS: - for node in tree.get(group, []): - yield from walk(node) - - -def wait_for_window(pattern): - def func(last_chance): - nodes = (node["name"] for node in walk(swaymsg(type="get_tree"))) - - if last_chance: - nodes = list(nodes) - machine.log(f"Last call! Current list of windows: {nodes}") - - return any(pattern in name for name in nodes) - - retry(func) - - -def collect_state_ui(name): - swaymsg(f"exec hakurei ps > '/tmp/{name}.ps'") - machine.wait_for_file(f"/tmp/{name}.ps") - machine.copy_from_vm(f"/tmp/{name}.ps", "") - swaymsg(f"exec hakurei --json ps > '/tmp/{name}.json'") - machine.wait_for_file(f"/tmp/{name}.json") - machine.copy_from_vm(f"/tmp/{name}.json", "") - machine.screenshot(name) - - -def check_state(name, enablements): - instances = json.loads(machine.succeed("sudo -u alice -i XDG_RUNTIME_DIR=/run/user/1000 hakurei --json ps")) - if len(instances) != 1: - raise Exception(f"unexpected state length {len(instances)}") - instance = instances[0] - - command = f"{name}-start" - if not (instance['container']['path'].startswith("/nix/store/")) or not (instance['container']['path'].endswith(command)): - raise Exception(f"unexpected path {instance['path']}") - - if len(instance['container']['args']) != 1 or instance['container']['args'][0] != command: - raise Exception(f"unexpected args {instance['args']}") - - if instance['enablements'] != enablements: - raise Exception(f"unexpected enablements {instance['enablements']['enablements']}") - - -def hakurei(command): - swaymsg(f"exec hakurei {command}") - - -start_all() -machine.wait_for_unit("multi-user.target") - -# To check hakurei's version: -print(machine.succeed("sudo -u alice -i hakurei version")) - -# Wait for Sway to complete startup: -machine.wait_for_file("/run/user/1000/wayland-1") -machine.wait_for_file("/tmp/sway-ipc.sock") - -# Run hakurei Go tests outside of nix build in the background: -swaymsg("exec hakurei-test") - -# Deny unmapped uid: -denyOutput = machine.fail("sudo -u untrusted -i hakurei exec &>/dev/stdout") -print(denyOutput) -denyOutputVerbose = machine.fail("sudo -u untrusted -i hakurei -v exec &>/dev/stdout") -print(denyOutputVerbose) - -# Direct hsu call: -userid = machine.succeed("sudo -u alice -i hsu") -if userid != "0": - raise Exception(f"unexpected userid: {userid}") - -# Verify hsu fault behaviour: -if denyOutput != "hsu: uid 1001 is not in the hsurc file\n": - raise Exception(f"unexpected deny output:\n{denyOutput}") -if denyOutputVerbose != "hsu: uid 1001 is not in the hsurc file\nhakurei: *cannot retrieve user id from setuid wrapper: current user is not in the hsurc file\n": - raise Exception(f"unexpected deny verbose output:\n{denyOutputVerbose}") - -# Verify timeout behaviour: -machine.succeed('sudo -u alice -i hakurei-check-linger-timeout > /var/tmp/linger-stdout 2> /var/tmp/linger-stderr || (cat /var/tmp/linger-stderr; false)') -linger_stdout = machine.succeed("cat /var/tmp/linger-stdout") -linger_stderr = machine.succeed("cat /var/tmp/linger-stderr") -if linger_stdout != "": - raise Exception(f"unexpected stdout: {linger_stdout}") -if linger_stderr != "init: timeout exceeded waiting for lingering processes\n": - raise Exception(f"unexpected stderr: {linger_stderr}") - -check_offset = 0 - - -def hakurei_identity(offset): - return 1+check_offset+offset - - -# Start hakurei permissive defaults outside Wayland session: -print(machine.succeed("sudo -u alice -i hakurei -v exec -a 0 touch /tmp/pd-bare-ok")) -machine.wait_for_file("/tmp/hakurei.0/tmpdir/0/pd-bare-ok") - -# Verify silent output permissive defaults: -output = machine.succeed("sudo -u alice -i hakurei exec -a 0 true &>/dev/stdout") -if output != "": - raise Exception(f"unexpected output\n{output}") - -# Verify silent output permissive defaults signal: -def silent_output_interrupt(flags): - swaymsg("exec foot") - wait_for_window("alice@machine") - # identity 0 does not have home-manager - machine.send_chars(f"exec hakurei exec {flags}-a 0 sh -c 'export PATH=/run/current-system/sw/bin:$PATH && touch /tmp/pd-silent-ready && sleep infinity' &>/tmp/pd-silent\n") - machine.wait_for_file("/tmp/hakurei.0/tmpdir/0/pd-silent-ready") - machine.succeed("rm /tmp/hakurei.0/tmpdir/0/pd-silent-ready") - machine.send_key("ctrl-c") - machine.wait_until_fails("pgrep foot") - machine.wait_until_fails(f"pgrep -u alice -f 'hakurei exec {flags}-a 0 '") - output = machine.succeed("cat /tmp/pd-silent && rm /tmp/pd-silent") - if output != "": - raise Exception(f"unexpected output\n{output}") - - -silent_output_interrupt("") -silent_output_interrupt("--dbus ") # this one is especially painful as it maintains a helper -silent_output_interrupt("--wayland -X --dbus --pulse ") - -# Verify graceful failure on bad Wayland display name: -print(machine.fail("sudo -u alice -i hakurei -v exec --wayland true")) - -# Start hakurei permissive defaults within Wayland session: -hakurei('-v exec --wayland --dbus --dbus-log notify-send -a "NixOS Tests" "Test notification" "Notification from within sandbox." && touch /tmp/dbus-ok') -machine.wait_for_file("/tmp/dbus-ok") -collect_state_ui("dbus_notify_exited") -# not in pid namespace, verify termination -machine.wait_until_fails("pgrep xdg-dbus-proxy") -machine.succeed("pkill -9 mako") - -# Check revert type selection: -hakurei("-v exec --wayland -X --dbus --pulse -u p0 foot && touch /tmp/p0-exit-ok") -wait_for_window("p0@machine") -print(machine.succeed("getfacl --absolute-names --omit-header --numeric /tmp/hakurei.0/runtime | grep 10000")) -hakurei("-v exec --wayland -X --dbus --pulse -u p1 foot && touch /tmp/p1-exit-ok") -wait_for_window("p1@machine") -print(machine.succeed("getfacl --absolute-names --omit-header --numeric /tmp/hakurei.0/runtime | grep 10000")) -machine.send_chars("exit\n") -machine.wait_for_file("/tmp/p1-exit-ok") -# Verify acl is kept alive: -print(machine.succeed("getfacl --absolute-names --omit-header --numeric /tmp/hakurei.0/runtime | grep 10000")) -machine.send_chars("exit\n") -machine.wait_for_file("/tmp/p0-exit-ok") -machine.fail("getfacl --absolute-names --omit-header --numeric /tmp/hakurei.0/runtime | grep 10000") - -# Check invalid identifier fd behaviour: -machine.fail('echo \'{"container":{"shell":"/proc/nonexistent","home":"/proc/nonexistent","path":"/proc/nonexistent"}}\' | sudo -u alice -i hakurei -v run --identifier-fd 32767 - 2>&1 | tee > /tmp/invalid-identifier-fd') -machine.wait_for_file("/tmp/invalid-identifier-fd") -print(machine.succeed('grep "^hakurei: cannot write identifier: bad file descriptor$" /tmp/invalid-identifier-fd')) - -# Check interrupt shim behaviour: -swaymsg("exec sh -c 'ne-foot; echo -n $? > /tmp/monitor-exit-code'") -wait_for_window(f"u0_a{hakurei_identity(0)}@machine") -machine.succeed("pkill -INT -f 'hakurei -v run '") -machine.wait_until_fails("pgrep foot") -machine.wait_for_file("/tmp/monitor-exit-code") -interrupt_exit_code = int(machine.succeed("cat /tmp/monitor-exit-code")) -if interrupt_exit_code != 230: - raise Exception(f"unexpected exit code {interrupt_exit_code}") - -# Check interrupt shim behaviour immediate termination: -swaymsg("exec sh -c 'ne-foot-immediate; echo -n $? > /tmp/monitor-exit-code'") -wait_for_window(f"u0_a{hakurei_identity(0)}@machine") -machine.succeed("pkill -INT -f 'hakurei -v run '") -machine.wait_until_fails("pgrep foot") -machine.wait_for_file("/tmp/monitor-exit-code") -interrupt_exit_code = int(machine.succeed("cat /tmp/monitor-exit-code")) -if interrupt_exit_code != 254: - raise Exception(f"unexpected exit code {interrupt_exit_code}") - -# Check shim SIGCONT from unexpected process behaviour: -swaymsg("exec sh -c 'ne-foot &> /tmp/shim-cont-unexpected-pid'") -wait_for_window(f"u0_a{hakurei_identity(0)}@machine") -machine.succeed("pkill -CONT -f 'hakurei shim'") -machine.succeed("pkill -INT -f 'hakurei -v run '") -machine.wait_until_fails("pgrep foot") -machine.wait_for_file("/tmp/shim-cont-unexpected-pid") -print(machine.succeed('grep "shim: got SIGCONT from unexpected process$" /tmp/shim-cont-unexpected-pid')) - -# Check setscheduler: -sched_unset = int(machine.succeed("sudo -u alice -i hakurei -v exec cat /proc/self/sched | grep '^policy' | tr -d ' ' | cut -d ':' -f 2")) -if sched_unset != 0: - raise Exception(f"unexpected unset policy: {sched_unset}") -sched_idle = int(machine.succeed("sudo -u alice -i hakurei -v exec --policy=idle cat /proc/self/sched | grep '^policy' | tr -d ' ' | cut -d ':' -f 2")) -if sched_idle != 5: - raise Exception(f"unexpected idle policy: {sched_idle}") -sched_rr = int(machine.succeed("sudo -u alice -i hakurei -v exec --policy=rr cat /proc/self/sched | grep '^policy' | tr -d ' ' | cut -d ':' -f 2")) -if sched_rr != 2: - raise Exception(f"unexpected round-robin policy: {sched_idle}") - -# Start app (foot) with Wayland enablement: -swaymsg("exec ne-foot") -wait_for_window(f"u0_a{hakurei_identity(0)}@machine") -machine.send_chars("clear; wayland-info && touch /var/tmp/client-ok\n") -machine.wait_for_file("/var/tmp/client-ok") -collect_state_ui("foot_wayland") -check_state("ne-foot", {"wayland": True}) -# Verify lack of acl on XDG_RUNTIME_DIR: -machine.fail(f"getfacl --absolute-names --omit-header --numeric /run/user/1000 | grep {hakurei_identity(0) + 10000}") -machine.send_chars("exit\n") -machine.wait_until_fails("pgrep foot") -machine.fail(f"getfacl --absolute-names --omit-header --numeric /run/user/1000 | grep {hakurei_identity(0) + 10000}") - -# Test pipewire-pulse: -swaymsg("exec pa-foot") -wait_for_window(f"u0_a{hakurei_identity(1)}@machine") -machine.send_chars("clear; pactl info && touch /var/tmp/pulse-ok\n") -machine.wait_for_file("/var/tmp/pulse-ok") -collect_state_ui("pulse_wayland") -check_state("pa-foot", {"wayland": True, "pipewire": True}) -machine.fail("find /tmp -maxdepth 1 -type d -name '.hakurei-shim-*' -print -exec false '{}' +") -machine.send_chars("exit\n") -machine.wait_until_fails("pgrep foot") -machine.wait_until_fails("pgrep -x hakurei") -machine.succeed("find /tmp -maxdepth 1 -type d -name '.hakurei-shim-*' -print -exec false '{}' +") -# Test PipeWire SecurityContext: -machine.succeed("sudo -u alice -i XDG_RUNTIME_DIR=/run/user/1000 hakurei -v exec --pulse pactl info") -machine.fail("sudo -u alice -i XDG_RUNTIME_DIR=/run/user/1000 hakurei -v exec --pulse pactl set-sink-mute @DEFAULT_SINK@ toggle") -# Test PipeWire direct access: -machine.succeed("sudo -u alice -i XDG_RUNTIME_DIR=/run/user/1000 pw-dump") -machine.fail("sudo -u alice -i XDG_RUNTIME_DIR=/run/user/1000 hakurei -v exec --pipewire pw-dump") - -# Test XWayland (foot does not support X): -swaymsg("exec x11-alacritty") -wait_for_window(f"u0_a{hakurei_identity(0)}@machine") -machine.send_chars("clear; glinfo && touch /var/tmp/x11-ok\n") -machine.wait_for_file("/var/tmp/x11-ok") -collect_state_ui("alacritty_x11") -check_state("x11-alacritty", {"x11": True}) -machine.send_chars("exit\n") -machine.wait_until_fails("pgrep alacritty") - -# Start app (foot) with direct Wayland access: -swaymsg("exec da-foot") -wait_for_window(f"u0_a{hakurei_identity(3)}@machine") -machine.send_chars("clear; wayland-info && touch /var/tmp/direct-ok\n") -collect_state_ui("foot_direct") -machine.wait_for_file("/var/tmp/direct-ok") -check_state("da-foot", {"wayland": True}) -# Verify acl on XDG_RUNTIME_DIR: -print(machine.succeed(f"getfacl --absolute-names --omit-header --numeric /run/user/1000 | grep {hakurei_identity(3) + 10000}")) -machine.send_chars("exit\n") -machine.wait_until_fails("pgrep foot") -# Verify acl cleanup on XDG_RUNTIME_DIR: -machine.wait_until_fails(f"getfacl --absolute-names --omit-header --numeric /run/user/1000 | grep {hakurei_identity(3) + 10000}") - -# Test syscall filter: -print(machine.fail("sudo -u alice -i XDG_RUNTIME_DIR=/run/user/1000 strace-failure")) - -# Start app (foot) with Wayland enablement from a terminal: -swaymsg("exec foot $SHELL -c '(ne-foot) & disown && exec $SHELL'") -wait_for_window(f"u0_a{hakurei_identity(0)}@machine") -machine.send_chars("clear; wayland-info && touch /var/tmp/term-ok\n") -machine.wait_for_file("/var/tmp/term-ok") -machine.send_key("alt-h") -machine.send_chars("clear; hakurei show $(hakurei ps --short) && touch /tmp/ps-show-ok && exec cat\n") -machine.wait_for_file("/tmp/ps-show-ok") -collect_state_ui("foot_wayland_term") -check_state("ne-foot", {"wayland": True}) -machine.send_key("alt-l") -machine.send_chars("exit\n") -wait_for_window("alice@machine") -machine.send_key("ctrl-c") -machine.wait_until_fails("pgrep foot") - -# Exit Sway and verify process exit status 0: -machine.wait_until_fails("pgrep -x hakurei") -swaymsg("exit", succeed=False) -machine.wait_for_file("/tmp/sway-exit-ok") - -# Print hakurei share and rundir contents: -print(machine.succeed("find /tmp/hakurei.0 " - + "-path '/tmp/hakurei.0/runtime/*/*' -prune -o " - + "-path '/tmp/hakurei.0/tmpdir/*/*' -prune -o " - + "-print")) -print(machine.succeed("find /run/user/1000/hakurei")) -machine.succeed("find /tmp -maxdepth 1 -type d -name '.hakurei-shim-*' -print -exec false '{}' +") - -# Verify go test status: -machine.wait_for_file("/tmp/hakurei-test-done") -print(machine.succeed("cat /tmp/hakurei-test.log")) -machine.wait_for_file("/tmp/hakurei-test-ok") -- cgit v1.3.1