diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index a41cae9f877..dd0921243a7 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -1311,6 +1311,7 @@ ./tasks/filesystems/btrfs.nix ./tasks/filesystems/cifs.nix ./tasks/filesystems/ecryptfs.nix + ./tasks/filesystems/envfs.nix ./tasks/filesystems/exfat.nix ./tasks/filesystems/ext.nix ./tasks/filesystems/f2fs.nix diff --git a/nixos/modules/tasks/filesystems/envfs.nix b/nixos/modules/tasks/filesystems/envfs.nix new file mode 100644 index 00000000000..ef8f655c532 --- /dev/null +++ b/nixos/modules/tasks/filesystems/envfs.nix @@ -0,0 +1,51 @@ +{ pkgs, config, lib, ... }: + +let + cfg = config.services.envfs; + mounts = { + "/usr/bin" = { + device = "none"; + fsType = "envfs"; + options = [ + "fallback-path=${pkgs.runCommand "fallback-path" {} '' + mkdir -p $out + ln -s ${pkgs.coreutils}/bin/env $out/env + ln -s ${config.system.build.binsh}/bin/sh $out/sh + ''}" + ]; + }; + "/bin" = { + device = "/usr/bin"; + fsType = "none"; + options = [ "bind" ]; + }; + }; +in { + options = { + services.envfs = { + enable = lib.mkEnableOption (lib.mdDoc "Envfs filesystem") // { + description = lib.mdDoc '' + Fuse filesystem that returns symlinks to executables based on the PATH + of the requesting process. This is useful to execute shebangs on NixOS + that assume hard coded locations in locations like /bin or /usr/bin + etc. + ''; + }; + package = lib.mkOption { + type = lib.types.package; + description = lib.mdDoc "Which package to use for the envfs."; + default = pkgs.envfs; + defaultText = lib.mdDoc "pkgs.envfs"; + }; + }; + }; + config = lib.mkIf (cfg.enable) { + environment.systemPackages = [ cfg.package ]; + # we also want these mounts in virtual machines. + fileSystems = if config.virtualisation ? qemu then lib.mkVMOverride mounts else mounts; + + # We no longer need those when using envfs + system.activationScripts.usrbinenv = lib.mkForce ""; + system.activationScripts.binsh = lib.mkForce ""; + }; +} diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 0a5144072e0..36a5c9843c2 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -195,6 +195,7 @@ in { engelsystem = handleTest ./engelsystem.nix {}; enlightenment = handleTest ./enlightenment.nix {}; env = handleTest ./env.nix {}; + envfs = handleTest ./envfs.nix {}; envoy = handleTest ./envoy.nix {}; ergo = handleTest ./ergo.nix {}; ergochat = handleTest ./ergochat.nix {}; diff --git a/nixos/tests/envfs.nix b/nixos/tests/envfs.nix new file mode 100644 index 00000000000..3f9cd1edb59 --- /dev/null +++ b/nixos/tests/envfs.nix @@ -0,0 +1,42 @@ +import ./make-test-python.nix ({ lib, pkgs, ... }: +let + pythonShebang = pkgs.writeScript "python-shebang" '' + #!/usr/bin/python + print("OK") + ''; + + bashShebang = pkgs.writeScript "bash-shebang" '' + #!/usr/bin/bash + echo "OK" + ''; +in +{ + name = "envfs"; + nodes.machine.services.envfs.enable = true; + + testScript = '' + start_all() + machine.wait_until_succeeds("mountpoint -q /usr/bin/") + machine.succeed( + "PATH=${pkgs.coreutils}/bin /usr/bin/cp --version", + # check fallback paths + "PATH= /usr/bin/sh --version", + "PATH= /usr/bin/env --version", + "PATH= test -e /usr/bin/sh", + "PATH= test -e /usr/bin/env", + # no stat + "! test -e /usr/bin/cp", + # also picks up PATH that was set after execve + "! /usr/bin/hello", + "PATH=${pkgs.hello}/bin /usr/bin/hello", + ) + + out = machine.succeed("PATH=${pkgs.python3}/bin ${pythonShebang}") + print(out) + assert out == "OK\n" + + out = machine.succeed("PATH=${pkgs.bash}/bin ${bashShebang}") + print(out) + assert out == "OK\n" + ''; +}) diff --git a/pkgs/tools/filesystems/envfs/default.nix b/pkgs/tools/filesystems/envfs/default.nix new file mode 100644 index 00000000000..de85a4ab0ca --- /dev/null +++ b/pkgs/tools/filesystems/envfs/default.nix @@ -0,0 +1,28 @@ +{ rustPlatform, lib, fetchFromGitHub, nixosTests }: +rustPlatform.buildRustPackage rec { + pname = "envfs"; + version = "1.0.0"; + src = fetchFromGitHub { + owner = "Mic92"; + repo = "envfs"; + rev = version; + hash = "sha256-aF8V1LwPGifFWoVxM0ydOnTX1pDVJ6HXevTxADJ/rsw="; + }; + cargoHash = "sha256-kw56tbe5zvWY5bI//dUqR1Rlumz8kOG4HeXiyEyL0I0="; + + passthru.tests = { + envfs = nixosTests.envfs; + }; + + postInstall = '' + ln -s envfs $out/bin/mount.envfs + ln -s envfs $out/bin/mount.fuse.envfs + ''; + meta = with lib; { + description = "Fuse filesystem that returns symlinks to executables based on the PATH of the requesting process."; + homepage = "https://github.com/Mic92/envfs"; + license = licenses.mit; + maintainers = with maintainers; [ mic92 ]; + platforms = platforms.linux; + }; +} diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix index 6c3b977a906..777422059fb 100644 --- a/pkgs/top-level/all-packages.nix +++ b/pkgs/top-level/all-packages.nix @@ -4405,6 +4405,8 @@ with pkgs; envsubst = callPackage ../tools/misc/envsubst { }; + envfs = callPackage ../tools/filesystems/envfs { }; + er-patcher = callPackage ../tools/games/er-patcher { }; errcheck = callPackage ../development/tools/errcheck { };