diff options
| author | Ophestra <cat@gensokyo.uk> | 2026-10-01 17:00:46 +0900 |
|---|---|---|
| committer | Ophestra <cat@gensokyo.uk> | 2026-10-01 21:44:30 +0900 |
| commit | 6a144f780ab62a5e6936d8f551dd03f51b11a153 (patch) | |
| tree | d3bb149739945becd2d0330d678f7ada620953f4 | |
| parent | a356dceedf0be29ae584866e540872ec1c048f59 (diff) | |
test/sharefs: migrate tests
This runs tests directly in the container, significantly reducing
overhead introduced by virtualisation.
Signed-off-by: Ophestra <cat@gensokyo.uk>
| -rw-r--r-- | .gitea/workflows/test.yml | 2 | ||||
| -rw-r--r-- | internal/workflows/doc.go | 9 | ||||
| -rw-r--r-- | internal/workflows/step.go | 22 | ||||
| -rw-r--r-- | internal/workflows/test.go | 35 | ||||
| -rw-r--r-- | test/flake.nix | 2 | ||||
| -rw-r--r-- | test/internal/testsuite/testsuite.go | 40 | ||||
| -rw-r--r-- | test/options.md | 1175 | ||||
| -rw-r--r-- | test/sharefs/configuration.nix | 44 | ||||
| -rw-r--r-- | test/sharefs/default.nix | 44 | ||||
| -rw-r--r-- | test/sharefs/main.go | 126 | ||||
| -rw-r--r-- | test/sharefs/test.py | 60 |
11 files changed, 226 insertions, 1333 deletions
diff --git a/.gitea/workflows/test.yml b/.gitea/workflows/test.yml index de11bccc..9cc6d82e 100644 --- a/.gitea/workflows/test.yml +++ b/.gitea/workflows/test.yml @@ -1 +1 @@ -{"name":"Test","on":["push"],"jobs":{"hakurei":{"name":"Hakurei","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 (race detector)","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":"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.sandbox"},{"name":"Upload test output","uses":"actions/upload-artifact@v3","with":{"name":"sandbox-vm-output","path":"result/*","retention-days":1}}]},"sandbox-race":{"name":"Sandbox (race detector)","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.sandbox-race"},{"name":"Upload test output","uses":"actions/upload-artifact@v3","with":{"name":"sandbox-race-vm-output","path":"result/*","retention-days":1}}]},"sharefs":{"name":"ShareFS","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.sharefs"},{"name":"Upload test output","uses":"actions/upload-artifact@v3","with":{"name":"sharefs-vm-output","path":"result/*","retention-days":1}}]},"check":{"name":"Flake checks","needs":["hakurei","race","sandbox","sandbox-race","sharefs"],"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":"export 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":"hakurei-${{ steps.dist-test.outputs.rev }}","path":"result/*","retention-days":1}}]}}} +{"name":"Test","on":["push"],"jobs":{"hakurei":{"name":"Hakurei","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 (race detector)","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":"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.sandbox"},{"name":"Upload test output","uses":"actions/upload-artifact@v3","with":{"name":"sandbox-vm-output","path":"result/*","retention-days":1}}]},"sandbox-race":{"name":"Sandbox (race detector)","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.sandbox-race"},{"name":"Upload test output","uses":"actions/upload-artifact@v3","with":{"name":"sandbox-race-vm-output","path":"result/*","retention-days":1}}]},"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 fuse and fs_mark","run":"apt-get update \u0026\u0026 apt-get install -y fuse3 fsmark"},{"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"},{"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 ubuntu"}]},"check":{"name":"Flake checks","needs":["hakurei","race","sandbox","sandbox-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":"hakurei-${{ steps.dist-test.outputs.rev }}","path":"result/*","retention-days":1}}]}}} diff --git a/internal/workflows/doc.go b/internal/workflows/doc.go index 0c7e94b9..e34c59c8 100644 --- a/internal/workflows/doc.go +++ b/internal/workflows/doc.go @@ -47,6 +47,7 @@ The container can be specified via docker-compose: runner: restart: always image: docker.io/gitea/act_runner:nightly + network_mode: host environment: CONFIG_FILE: /config.yaml GITEA_INSTANCE_URL: "https://git.gensokyo.uk" @@ -62,17 +63,21 @@ The container can be specified via docker-compose: Before starting the container, configure act_runner via config.yaml: container: - network: host options: -e PAGER=cat -e MBF_CACHE_DIR=/rosa -e MBF_POISON_OPEN=1 -v /var/lib/rosa:/rosa + --security-opt='unmask=/proc/*' + --cap-add=SYS_ADMIN --device=/dev/kvm + --device=/dev/fuse valid_volumes: - /var/lib/rosa where /var/lib/rosa is the absolute pathname of the cache directory in the init namespace. Setting MBF_POISON_OPEN enables cmd/mbf to run as root. It is also a good idea here to set runner.capacity to reflect the capacity of the guest, so -jobs can be consumed quicker. +jobs can be consumed quicker. Removing mount points covering /proc enables +testing of cmd/hakurei. Exposing the fuse device and adding capability SYS_ADMIN +enables testing of cmd/sharefs. Build a statically-linked cmd/mbf: diff --git a/internal/workflows/step.go b/internal/workflows/step.go index 423a48ff..219ed78b 100644 --- a/internal/workflows/step.go +++ b/internal/workflows/step.go @@ -12,6 +12,16 @@ var fixup = Step{ // checkout is the standard actions/checkout step. var checkout = Step{Name: "Checkout", Uses: "actions/checkout@v4"} +// toolchain is a step for setting up the Go toolchain. +var toolchain = Step{ + Name: "Set up Go toolchain", + Uses: "actions/setup-go@v6", + + With: []KV[any]{ + {"go-version-file", "go.mod"}, + }, +} + // newUploadArtifact returns an actions/upload-artifact step uploading // everything in the result directory as the specified name. func newUploadArtifact(display, name string) Step { @@ -33,13 +43,23 @@ func newCIRequest(display, name, id string) Step { return Step{ Name: "Request " + display, ID: id, - Run: "export HAKUREI_REV=\"$(git rev-parse --short HEAD)\" && " + + Run: "HAKUREI_REV=\"$(git rev-parse --short HEAD)\" && " + "/rosa/bin/mbf ci " + name + " " + ". \"$(cat cmd/dist/VERSION)-$HAKUREI_REV\" && " + "echo \"rev=$HAKUREI_REV\" >> \"$GITHUB_OUTPUT\"", } } +// install is a step to unpack and deploy a hakurei distribution created by a +// step with identifier dist. +var install = Step{ + Name: "Install hakurei", + Run: "HAKUREI_VERSION=\"$(cat cmd/dist/VERSION)-${{ steps.dist.outputs.rev }}\" && " + + "tar xf \"result/hakurei-$HAKUREI_VERSION-amd64.tar.gz\" && " + + "\"./hakurei-$HAKUREI_VERSION-amd64/install.sh\" && " + + "sudo -u ubuntu hakurei version", +} + // newNixOSTest returns a step for running the named NixOS test. func newNixOSTest(name string) Step { return Step{ diff --git a/internal/workflows/test.go b/internal/workflows/test.go index 88e91a22..28307b53 100644 --- a/internal/workflows/test.go +++ b/internal/workflows/test.go @@ -53,12 +53,40 @@ var _ = (&Workflow{ {"sharefs", Job{ Name: "ShareFS", - On: "nix", + On: "rosa", Steps: []Step{ + fixup, checkout, - newNixOSTest("sharefs"), - newUploadArtifact("test output", "sharefs-vm-output"), + toolchain, + + { + Name: "install fuse and fs_mark", + Run: "apt-get update && apt-get install -y fuse3 fsmark", + }, + + newCIRequest("distribution", "dist -o result", "dist"), + install, + + { + Name: "Mount sharefs", + Run: "useradd " + + "-ru 1023 " + + "-md /var/lib/sdcard " + + "-k /var/empty " + + "-s /sbin/nologin " + + "media_rw && install -dm0 /sdcard && " + + "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 && rm -rf result && " + + "go run -tags=testsuite ./test/sharefs ubuntu", + }, }, }}, @@ -71,7 +99,6 @@ var _ = (&Workflow{ "race", "sandbox", "sandbox-race", - "sharefs", }, Steps: []Step{ diff --git a/test/flake.nix b/test/flake.nix index 01a07f78..b106ec7a 100644 --- a/test/flake.nix +++ b/test/flake.nix @@ -55,8 +55,6 @@ withRace = true; }; - sharefs = callPackage ./sharefs { inherit system self; }; - formatting = runCommandLocal "check-formatting" { nativeBuildInputs = [ nixfmt ]; } '' cd ${./.} diff --git a/test/internal/testsuite/testsuite.go b/test/internal/testsuite/testsuite.go new file mode 100644 index 00000000..4b70da34 --- /dev/null +++ b/test/internal/testsuite/testsuite.go @@ -0,0 +1,40 @@ +// Package testsuite provides many quick-and-dirty integration testing utilities. +package testsuite + +import ( + "log" + "os" + "os/exec" + "os/signal" + "syscall" +) + +func init() { + log.SetFlags(0) + log.SetPrefix("testsuite: ") + + if os.Geteuid() != 0 { + log.Fatal("this program must run as root") + } +} + +// ReceiveSignals blocks until a termination signal arrives, and terminates. +func ReceiveSignals() { + s := make(chan os.Signal, 3) + signal.Notify(s, os.Interrupt, syscall.SIGTERM, syscall.SIGHUP) + log.Fatalf("terminating on signal %s", <-s) +} + +// MustRun runs command and terminates the testsuite on error. +func MustRun(command ...string) { + cmd := exec.Command(command[0], command[1:]...) + cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr + if err := cmd.Run(); err != nil { + log.Fatal(err) + } +} + +// MustRunAs wraps [MustRun] for sudo. +func MustRunAs(username string, command ...string) { + MustRun(append([]string{"sudo", "-u", username}, command...)...) +} diff --git a/test/options.md b/test/options.md deleted file mode 100644 index 6f6acddd..00000000 --- a/test/options.md +++ /dev/null @@ -1,1175 +0,0 @@ -## environment\.hakurei\.enable - - - -Whether to enable hakurei\. - - - -*Type:* -boolean - - - -*Default:* - -```nix -false -``` - - - -*Example:* - -```nix -true -``` - - - -## environment\.hakurei\.package - - - -The hakurei package to use\. - - - -*Type:* -package - - - -*Default:* - -```nix -<derivation hakurei-static-x86_64-unknown-linux-musl-0.4.3> -``` - - - -## environment\.hakurei\.apps - -Declaratively configured hakurei apps\. - - - -*Type:* -attribute set of (submodule) - - - -*Default:* - -```nix -{ } -``` - - - -## environment\.hakurei\.apps\.\<name>\.enablements\.dbus - - - -Whether to proxy D-Bus\. - - - -*Type:* -null or boolean - - - -*Default:* - -```nix -true -``` - - - -## environment\.hakurei\.apps\.\<name>\.enablements\.pipewire - - - -Whether to share the PipeWire server via pipewire-pulse on a SecurityContext socket\. - - - -*Type:* -null or boolean - - - -*Default:* - -```nix -true -``` - - - -## environment\.hakurei\.apps\.\<name>\.enablements\.wayland - - - -Whether to share the Wayland server via security-context-v1\. - - - -*Type:* -null or boolean - - - -*Default:* - -```nix -true -``` - - - -## environment\.hakurei\.apps\.\<name>\.enablements\.x11 - - - -Whether to share the X11 socket and allow connection\. - - - -*Type:* -null or boolean - - - -*Default:* - -```nix -false -``` - - - -## environment\.hakurei\.apps\.\<name>\.packages - - - -List of extra packages to install via home-manager\. - - - -*Type:* -list of package - - - -*Default:* - -```nix -[ ] -``` - - - -## environment\.hakurei\.apps\.\<name>\.args - - - -Custom args\. -Setting this to null will default to script name\. - - - -*Type:* -null or (list of string) - - - -*Default:* - -```nix -null -``` - - - -## environment\.hakurei\.apps\.\<name>\.command - - - -Command to run as the target user\. -Setting this to null will default command to launcher name\. -Has no effect when script is set\. - - - -*Type:* -null or string - - - -*Default:* - -```nix -null -``` - - - -## environment\.hakurei\.apps\.\<name>\.dbus\.session - - - -D-Bus session bus custom configuration\. -Setting this to null will enable built-in defaults\. - - - -*Type:* -null or (function that evaluates to a(n) anything) - - - -*Default:* - -```nix -null -``` - - - -## environment\.hakurei\.apps\.\<name>\.dbus\.system - - - -D-Bus system bus custom configuration\. -Setting this to null will disable the system bus proxy\. - - - -*Type:* -null or anything - - - -*Default:* - -```nix -null -``` - - - -## environment\.hakurei\.apps\.\<name>\.devel - - - -Whether to enable debugging-related kernel interfaces\. - - - -*Type:* -boolean - - - -*Default:* - -```nix -false -``` - - - -*Example:* - -```nix -true -``` - - - -## environment\.hakurei\.apps\.\<name>\.device - - - -Whether to enable access to all devices\. - - - -*Type:* -boolean - - - -*Default:* - -```nix -false -``` - - - -*Example:* - -```nix -true -``` - - - -## environment\.hakurei\.apps\.\<name>\.env - - - -Environment variables to set for the initial process in the sandbox\. - - - -*Type:* -null or (attribute set of string) - - - -*Default:* - -```nix -null -``` - - - -## environment\.hakurei\.apps\.\<name>\.extraConfig - - - -Extra home-manager configuration\. - - - -*Type:* -anything - - - -*Default:* - -```nix -{ } -``` - - - -## environment\.hakurei\.apps\.\<name>\.extraPaths - - - -Extra paths to make available to the container\. - - - -*Type:* -list of attribute set of anything - - - -*Default:* - -```nix -[ ] -``` - - - -## environment\.hakurei\.apps\.\<name>\.gpu - - - -Target process GPU and driver access\. -Setting this to null will enable GPU whenever X or Wayland is enabled\. - - - -*Type:* -null or boolean - - - -*Default:* - -```nix -null -``` - - - -## environment\.hakurei\.apps\.\<name>\.groups - - - -List of groups to inherit from the privileged user\. - - - -*Type:* -list of string - - - -*Default:* - -```nix -[ ] -``` - - - -## environment\.hakurei\.apps\.\<name>\.hostAbstract - - - -Whether to enable share abstract unix socket scope\. - - - -*Type:* -boolean - - - -*Default:* - -```nix -false -``` - - - -*Example:* - -```nix -true -``` - - - -## environment\.hakurei\.apps\.\<name>\.hostNet - - - -Whether to enable share host net namespace\. - - - -*Type:* -boolean - - - -*Default:* - -```nix -true -``` - - - -*Example:* - -```nix -true -``` - - - -## environment\.hakurei\.apps\.\<name>\.identity - - - -Application identity\. Identity 0 is reserved for system services\. - - - -*Type:* -integer between 1 and 9999 (both inclusive) - - - -## environment\.hakurei\.apps\.\<name>\.insecureWayland - - - -Whether to enable direct access to the Wayland socket\. - - - -*Type:* -boolean - - - -*Default:* - -```nix -false -``` - - - -*Example:* - -```nix -true -``` - - - -## environment\.hakurei\.apps\.\<name>\.mapRealUid - - - -Whether to enable mapping to priv-user uid\. - - - -*Type:* -boolean - - - -*Default:* - -```nix -false -``` - - - -*Example:* - -```nix -true -``` - - - -## environment\.hakurei\.apps\.\<name>\.multiarch - - - -Whether to enable multiarch kernel-level support\. - - - -*Type:* -boolean - - - -*Default:* - -```nix -false -``` - - - -*Example:* - -```nix -true -``` - - - -## environment\.hakurei\.apps\.\<name>\.name - - - -Name of the app’s launcher script\. - - - -*Type:* -string - - - -## environment\.hakurei\.apps\.\<name>\.nix - - - -Whether to enable nix daemon access\. - - - -*Type:* -boolean - - - -*Default:* - -```nix -false -``` - - - -*Example:* - -```nix -true -``` - - - -## environment\.hakurei\.apps\.\<name>\.path - - - -Custom executable path\. -Setting this to null will default to the start script\. - - - -*Type:* -null or string - - - -*Default:* - -```nix -null -``` - - - -## environment\.hakurei\.apps\.\<name>\.schedPolicy - - - -Scheduling policy to set for the container\. -The zero value retains the current scheduling policy\. - - - -*Type:* -null or one of “fifo”, “rr”, “batch”, “idle”, “deadline”, “ext” - - - -*Default:* - -```nix -null -``` - - - -## environment\.hakurei\.apps\.\<name>\.schedPriority - - - -Scheduling priority to set for the container\. - - - -*Type:* -null or integer between 1 and 99 (both inclusive) - - - -*Default:* - -```nix -null -``` - - - -## environment\.hakurei\.apps\.\<name>\.script - - - -Application launch script\. - - - -*Type:* -null or string - - - -*Default:* - -```nix -null -``` - - - -## environment\.hakurei\.apps\.\<name>\.share - - - -Package containing share files\. -Setting this to null will default package name to wrapper name\. - - - -*Type:* -null or package - - - -*Default:* - -```nix -null -``` - - - -## environment\.hakurei\.apps\.\<name>\.shareRuntime - - - -Whether to enable sharing of XDG_RUNTIME_DIR between containers under the same identity\. - - - -*Type:* -boolean - - - -*Default:* - -```nix -false -``` - - - -*Example:* - -```nix -true -``` - - - -## environment\.hakurei\.apps\.\<name>\.shareTmpdir - - - -Whether to enable sharing of TMPDIR between containers under the same identity\. - - - -*Type:* -boolean - - - -*Default:* - -```nix -false -``` - - - -*Example:* - -```nix -true -``` - - - -## environment\.hakurei\.apps\.\<name>\.shareUid - - - -Whether to enable sharing identity with another application\. - - - -*Type:* -boolean - - - -*Default:* - -```nix -false -``` - - - -*Example:* - -```nix -true -``` - - - -## environment\.hakurei\.apps\.\<name>\.tty - - - -Whether to enable access to the controlling terminal\. - - - -*Type:* -boolean - - - -*Default:* - -```nix -false -``` - - - -*Example:* - -```nix -true -``` - - - -## environment\.hakurei\.apps\.\<name>\.useCommonPaths - - - -Whether to enable common extra paths\. - - - -*Type:* -boolean - - - -*Default:* - -```nix -true -``` - - - -*Example:* - -```nix -true -``` - - - -## environment\.hakurei\.apps\.\<name>\.userns - - - -Whether to enable user namespace creation\. - - - -*Type:* -boolean - - - -*Default:* - -```nix -false -``` - - - -*Example:* - -```nix -true -``` - - - -## environment\.hakurei\.apps\.\<name>\.verbose - - - -Whether to enable launchers with verbose output\. - - - -*Type:* -boolean - - - -*Default:* - -```nix -false -``` - - - -*Example:* - -```nix -true -``` - - - -## environment\.hakurei\.apps\.\<name>\.wait_delay - - - -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\. - - - -*Type:* -null or signed integer - - - -*Default:* - -```nix -null -``` - - - -## environment\.hakurei\.commonPaths - - - -Common extra paths to make available to the container\. - - - -*Type:* -list of attribute set of anything - - - -*Default:* - -```nix -[ ] -``` - - - -## environment\.hakurei\.extraHomeConfig - - - -Extra home-manager configuration to merge with all target users\. - - - -*Type:* -anything - - - -## environment\.hakurei\.hsuPackage - - - -The hsu package to use\. - - - -*Type:* -package - - - -*Default:* - -```nix -<derivation hakurei-hsu-0.4.3> -``` - - - -## environment\.hakurei\.sharefs\.package - - - -The sharefs package to use\. - - - -*Type:* -package - - - -*Default:* - -```nix -<derivation sharefs> -``` - - - -## environment\.hakurei\.sharefs\.group - - - -Name of the group to run the sharefs daemon as\. - - - -*Type:* -string - - - -*Default:* - -```nix -"sharefs" -``` - - - -## environment\.hakurei\.sharefs\.name - - - -Host path to mount sharefs on\. - - - -*Type:* -string - - - -*Default:* - -```nix -"/sdcard" -``` - - - -## environment\.hakurei\.sharefs\.source - - - -Writable backing directory\. Setting this to null disables sharefs\. - - - -*Type:* -null or string - - - -*Default:* - -```nix -null -``` - - - -## environment\.hakurei\.sharefs\.user - - - -Name of the user to run the sharefs daemon as\. - - - -*Type:* -string - - - -*Default:* - -```nix -"sharefs" -``` - - - -## environment\.hakurei\.shell - - - -Absolute path to preferred shell\. - - - -*Type:* -string - - - -*Default:* - -```nix -"/run/current-system/sw/bin/bash" -``` - - - -## environment\.hakurei\.stateDir - - - -The state directory where app home directories are stored\. - - - -*Type:* -string - - - -## environment\.hakurei\.users - - - -Users allowed to spawn hakurei apps and their corresponding hakurei identity\. - - - -*Type:* -attribute set of integer between 0 and 99 (both inclusive) - - - -*Default:* - -```nix -{ } -``` - - diff --git a/test/sharefs/configuration.nix b/test/sharefs/configuration.nix deleted file mode 100644 index 05d67dcb..00000000 --- a/test/sharefs/configuration.nix +++ /dev/null @@ -1,44 +0,0 @@ -{ pkgs, ... }: -{ - users.users = { - alice = { - isNormalUser = true; - description = "Alice Foobar"; - password = "foobar"; - uid = 1000; - }; - }; - - home-manager.users.alice.home.stateVersion = "24.11"; - - # Automatically login on tty1 as a normal user: - services.getty.autologinUser = "alice"; - - environment = { - # For benchmarking sharefs: - systemPackages = [ pkgs.fsmark ]; - }; - - virtualisation = { - # Hopefully reduces spurious test failures: - memorySize = if pkgs.stdenv.hostPlatform.is32bit then 2046 else 8192; - - diskSize = 6 * 1024; - - qemu.options = [ - # Increase test performance: - "-smp 16" - ]; - }; - - environment.hakurei = rec { - enable = true; - stateDir = "/var/lib/hakurei"; - sharefs.source = "${stateDir}/sdcard"; - users.alice = 0; - - extraHomeConfig = { - home.stateVersion = "23.05"; - }; - }; -} diff --git a/test/sharefs/default.nix b/test/sharefs/default.nix deleted file mode 100644 index e963eaa6..00000000 --- a/test/sharefs/default.nix +++ /dev/null @@ -1,44 +0,0 @@ -{ - testers, - - system, - self, -}: -testers.nixosTest { - name = "sharefs"; - nodes.machine = - { options, pkgs, ... }: - let - fhs = - let - hakurei = options.environment.hakurei.package.default; - in - pkgs.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: - (pkgs.writeShellScriptBin "sharefs-workload-hakurei-tests" '' - cp -r "${self.packages.${system}.hakurei.src}" "/sdcard/hakurei" && cd "/sdcard/hakurei" - ${fhs}/bin/hakurei-fhs -c 'ROSA_SKIP_BINFMT=1 CC="clang -O3 -Werror" go test ./...' - '') - ]; - - imports = [ - ./configuration.nix - - self.nixosModules.hakurei - self.inputs.home-manager.nixosModules.home-manager - ]; - }; - - testScript = builtins.readFile ./test.py; -} diff --git a/test/sharefs/main.go b/test/sharefs/main.go new file mode 100644 index 00000000..536a61b3 --- /dev/null +++ b/test/sharefs/main.go @@ -0,0 +1,126 @@ +//go:build testsuite + +// The sharefs test program checks cli behaviour and exercises the filesystem +// implemented by cmd/sharefs using fs_mark. +package main + +import ( + "errors" + "log" + "os" + "os/exec" + "slices" + "strings" + + "hakurei.app/test/internal/testsuite" +) + +// checkBadOpts invokes cmd/sharefs with the specified options and compares +// the resulting error message. +func checkBadOpts(username, opts, want string) { + var buf strings.Builder + buf.Grow(len(want)) + + sudo := []string{"sudo", "-u", username, "-i", "--"} + if username == "root" { + sudo = nil + } + + a := slices.Concat(sudo, []string{ + "sharefs", + "-f", + "-o", "source=/etc," + opts, + "/mnt", + }) + cmd := exec.Command(a[0], a[1:]...) + cmd.Stderr = &buf + err := cmd.Run() + if err == nil { + log.Fatalf("opts=%q, unexpected success", opts) + } + if e, ok := errors.AsType[*exec.ExitError](err); !ok { + log.Fatal(err) + } else if !e.Exited() { + log.Fatal(e) + } + + if got := buf.String(); got != want { + log.Fatalf("opts=%q\n\t got:%q\n\twant:%q", opts, got, want) + } +} + +func main() { + go testsuite.ReceiveSignals() + + if err := os.Mkdir("result", 0755); err != nil { + log.Fatal(err) + } + + if len(os.Args) != 2 { + log.Fatal("expecting 1 argument") + } + username := os.Args[1] + + done := make(chan struct{}) + go func() { + defer close(done) + + testsuite.MustRun( + "fs_mark", + "-v", + "-d", "/sdcard/fs_mark", + "-l", "result/fs_mark.log", + ) + }() + + log.Println("checking malformed setuid/setgid representation") + checkBadOpts(username, "setuid=ff", "sharefs: invalid value for option setuid\n") + checkBadOpts(username, "setgid=ff", "sharefs: invalid value for option setgid\n") + + log.Println("checking bounds check for setuid/setgid") + checkBadOpts(username, "setuid=0", "sharefs: invalid value for option setuid\n") + checkBadOpts(username, "setgid=0", "sharefs: invalid value for option setgid\n") + checkBadOpts(username, "setuid=-1", "sharefs: invalid value for option setuid\n") + checkBadOpts(username, "setgid=-1", "sharefs: invalid value for option setgid\n") + + log.Println("checking non-root setuid/setgid") + checkBadOpts(username, "setuid=1023", "sharefs: setuid and setgid has no effect when not starting as root\n") + checkBadOpts(username, "setgid=1023", "sharefs: setuid and setgid has no effect when not starting as root\n") + checkBadOpts(username, "setuid=1023,setgid=1023", "sharefs: setuid and setgid has no effect when not starting as root\n") + checkBadOpts(username, "mkdir", "sharefs: mkdir has no effect when not starting as root\n") + + log.Println("checking root without setuid/setgid") + checkBadOpts("root", "allow_other", "sharefs: setuid and setgid must not be 0\n") + checkBadOpts("root", "setuid=1023", "sharefs: setuid and setgid must not be 0\n") + checkBadOpts("root", "setgid=1023", "sharefs: setuid and setgid must not be 0\n") + + log.Println("verifying mount point") + if err := os.Remove("/mnt"); err != nil { + log.Fatal(err) + } + + log.Println("checking unprivileged mount/unmount") + testsuite.MustRunAs(username, "-i", "mkdir", "/tmp/sdcard", "/tmp/persistent") + testsuite.MustRunAs(username, "-i", "sharefs", "-o", "source=/tmp/persistent", "/tmp/sdcard") + testsuite.MustRunAs(username, "-i", "touch", "/tmp/sdcard/check") + testsuite.MustRunAs(username, "-i", "umount", "/tmp/sdcard") + testsuite.MustRunAs(username, "-i", "rm", "/tmp/persistent/check") + testsuite.MustRunAs(username, "-i", "rmdir", "/tmp/sdcard", "/tmp/persistent") + + log.Println("waiting for fs_mark to complete") + <-done + + const ( + backingDir = "/var/lib/sdcard" + sharefsUser = "media_rw" + ) + log.Println("checking permissions") + testsuite.MustRunAs(sharefsUser, "touch", backingDir+"/fs_mark/.check") + testsuite.MustRunAs(sharefsUser, "rm", backingDir+"/fs_mark/.check") + testsuite.MustRunAs(username, "-i", "rm", "-rf", "/sdcard/fs_mark") + if _, err := os.ReadDir(backingDir + "/fs_mark"); err == nil { + log.Fatal("fs_mark directory was not removed") + } else if !errors.Is(err, os.ErrNotExist) { + log.Fatal(err) + } +} diff --git a/test/sharefs/test.py b/test/sharefs/test.py deleted file mode 100644 index 4b925c9c..00000000 --- a/test/sharefs/test.py +++ /dev/null @@ -1,60 +0,0 @@ -start_all() -machine.wait_for_unit("multi-user.target") - -# To check sharefs version: -print(machine.succeed("sharefs -V")) - -# Make sure sharefs started: -machine.wait_for_unit("sdcard.mount") - -machine.succeed("mkdir /mnt") -def check_bad_opts_output(opts, want, source="/etc", privileged=False): - output = machine.fail(("" if privileged else "sudo -u alice -i ") + f"sharefs -f -o source={source},{opts} /mnt 2>&1") - if output != want: - raise Exception(f"unexpected output: {output}") - -# Malformed setuid/setgid representation: -check_bad_opts_output("setuid=ff", "sharefs: invalid value for option setuid\n") -check_bad_opts_output("setgid=ff", "sharefs: invalid value for option setgid\n") - -# Bounds check for setuid/setgid: -check_bad_opts_output("setuid=0", "sharefs: invalid value for option setuid\n") -check_bad_opts_output("setgid=0", "sharefs: invalid value for option setgid\n") -check_bad_opts_output("setuid=-1", "sharefs: invalid value for option setuid\n") -check_bad_opts_output("setgid=-1", "sharefs: invalid value for option setgid\n") - -# Non-root setuid/setgid: -check_bad_opts_output("setuid=1023", "sharefs: setuid and setgid has no effect when not starting as root\n") -check_bad_opts_output("setgid=1023", "sharefs: setuid and setgid has no effect when not starting as root\n") -check_bad_opts_output("setuid=1023,setgid=1023", "sharefs: setuid and setgid has no effect when not starting as root\n") -check_bad_opts_output("mkdir", "sharefs: mkdir has no effect when not starting as root\n") - -# Starting as root without setuid/setgid: -check_bad_opts_output("allow_other", "sharefs: setuid and setgid must not be 0\n", privileged=True) -check_bad_opts_output("setuid=1023", "sharefs: setuid and setgid must not be 0\n", privileged=True) -check_bad_opts_output("setgid=1023", "sharefs: setuid and setgid must not be 0\n", privileged=True) - -# Make sure nothing actually got mounted: -machine.fail("umount /mnt") -machine.succeed("rmdir /mnt") - -# Unprivileged mount/unmount: -machine.succeed("sudo -u alice -i mkdir /home/alice/{sdcard,persistent}") -machine.succeed("sudo -u alice -i sharefs -o source=/home/alice/persistent /home/alice/sdcard") -machine.succeed("sudo -u alice -i touch /home/alice/sdcard/check") -machine.succeed("sudo -u alice -i umount /home/alice/sdcard") -machine.succeed("sudo -u alice -i rm /home/alice/persistent/check") -machine.succeed("sudo -u alice -i rmdir /home/alice/{sdcard,persistent}") - -# Benchmark sharefs: -machine.succeed("fs_mark -v -d /sdcard/fs_mark -l /tmp/fs_log.txt") -machine.copy_from_vm("/tmp/fs_log.txt", "") - -# Check permissions: -machine.succeed("sudo -u sharefs touch /var/lib/hakurei/sdcard/fs_mark/.check") -machine.succeed("sudo -u sharefs rm /var/lib/hakurei/sdcard/fs_mark/.check") -machine.succeed("sudo -u alice rm -rf /sdcard/fs_mark") -machine.fail("ls /var/lib/hakurei/sdcard/fs_mark") - -# Run hakurei tests on sharefs: -machine.succeed("sudo -u alice -i sharefs-workload-hakurei-tests") |
