Nuenv: an experimental Nushell environment for Nix

As a fun and ultimately quite fruitful experiment, I created an environment for Nix based on the amazingly powerful and expressive Nushell as an alternative to Nix’s Bash-based standard environment. You can read about it on the Determinate Systems blog and check out the repo on GitHub:

While Nuenv isn’t yet particularly powerful, it does open up some pretty intriguing horizons, as Nushell offers an expansive feature set that includes:

  • Built-in support for JSON, YAML, TOML, SQLite, and others
  • Custom commands with typed inputs that feel more like CLI tools than Bash functions
  • Dramatically improved handling of things like strings and environment variables
  • A broad palette of utilities that replace and go beyond the standard GNU coreutils
  • Scoping mechanisms like modules and overlays

Plus, Nushell is a fairly new and very actively updated project, meaning that Nuenv can take advantage of any forward progress on the Nushell side.

While I encourage you to check out the blog post and the repo, you can give it a try immediately if you have Nix installed and flakes enabled:

# Build a basic package and see the pretty log output
nix build --print-build-logs "github:DeterminateSystems/nuenv"

# Use the Nuenv shell
nix develop "github:DeterminateSystems/nuenv#nuenv"
# Run `substituteInPlace --help` for example help output

# See which custom commands are available in Nuenv
nix run "github:DeterminateSystems/nuenv#nuenv-commands"

You can even write your own Nushell-based derivations. Here’s an example flake:

{
  inputs = {
    nixpkgs.url = "nixpkgs"; # Make sure to use a very recent Nixpkgs
    nuenv.url = "github:DeterminateSystems/nuenv";
  };

  outputs = { self, nixpkgs, nuenv }: let
    overlays = [ nuenv.overlays.default ];
    systems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ];
    forAllSystems = f: nixpkgs.lib.genAttrs systems (system: f {
      inherit system;
      pkgs = import nixpkgs { inherit overlays system; };
    });
  in {
    packages = forAllSystems ({ pkgs, system }: {
      default = pkgs.nuenv.mkDerivation {
        name = "hello";
        src = ./.;
        inherit system;
        packages = with pkgs; [ hello ];
        # This script is Nushell, not Bash
        build = ''
          hello --greeting $"($env.MESSAGE)" | save hello.txt
          let out = $"($env.out)/share"
          mkdir $out
          cp hello.txt $out
        '';
        MESSAGE = "My custom Nuenv derivation!";
      };
    });
  };
}

Modify at will and run nix build --print-build-logs to see the results.

This has been a really fun exercise! Please let us know what you think.

24 Likes

Beautiful. Just today I thought about how the brittleness of bash is pervasive throughout the derivations in the ecosystem.

Thank you for this, @lucperkins .

3 Likes

Glad you liked it! It definitely opens up some very intriguing possibilities. For example, I’ve been playing around with Rust packaging using Nuenv:

https://github.com/DeterminateSystems/nuenv/pull/1

Still a lot of work to do there but shows what’s possible.

6 Likes

Nice work, I was wondering about this option some months ago, really thank you for making my dream come true. :heart_eyes_cat:

Some random ideas/opinions:

Checkout this related discussion where I was thinking what would make a good bash alternative without thinking about retro compatibility (there is a RFC for OSH/Oilsh but it’s more twards retrocompatibility)

I mixed nushell with devshell to interoperate with bash (but I using it from fish), because I loved Nushell as script lang but think fish has better repl UX. Anyway, the fun part is it expect two types, if is it is a string is just a script like your build, if it is a list of strings, the last is the ‘main script’ and others are params, ie:

#  devshell module example
{
    files.nus.my-hosts   = ''
    # list of hosts machines
    {
      dev: {
        gsm:  18.221.226.28
        sate: 18.221.226.14
      }
    }'';
    files.nus.my-ssh     = ["environ" "node" "cmd" ''
      # run command in nodes over ssh
      let IP = (my-hosts|get $environ|get $node)
      ssh $"($env.NETWORK_USER)@($IP)" $cmd
    ''];
    files.nus.my-ctl     = ["environ" "node" "cmd" ''
      # run systemctl on nodes
      my-ssh $environ $node $"systemctl ($cmd)"|lines
    ''];
}

And I export (to devshell env) as both nushell function (to call from other nushell alias) and nushell main files (to call from bash/fish) that import all functions.

Other related idea for that is that a simple script could create an alternatve to yj (TOML ↔ YAML ↔ JSON converter) with smaller closure size than Remarshal used by pkgs-lib/formats

My only concern against Nushell is an issue related to how they handle arguments (I’m not sure if my comment is included as closed)

1 Like

I almost always need to supplement my Bash scripts with tools like awk , sed , curl , jq , and wget to get even basic things done

That is the POSIX philosophy and how shells work and not unique to bash. You have one tool and that does one thing really really good, instead of having one tool that does everything kinda mediocre.

This enables you to avoid those un-fun variable name clashes that pop up so often in Bash.

bash has lots of problems with variables like that you can mangle anything into a string with no problem but I am hearing about this specific issue for the first time and also cannot remember that it was a problem for me in the past.

If can offer a dramatically improved developer experience around derivation.

Contradicting to that it would also require people to learn nu which unlike bash most people will not be at least a little bit familiar.

FWIW, I’ve found that nushell is easier for me to understand, despite never using nushell.

4 Likes

I don’t think that familiarity is necessarily a condition for good DX. For instance, I have written way more Perl than let’s say Elm (and I think I’m by far not the only one), but that’s not an argument for Perl’s DX.

3 Likes

Personally, I think it’s quite nice to work with a shell that provides analogues to those tools that are designed from the ground up to interoperate seamlessly via a shared interface/data model.

I’m glad you haven’t had issues with this! But personally I find it much cleaner to have separate “systems” for dealing with environment variables and regular variables. I personally get bitten by this a lot :man_shrugging:

As Ma27 expressed, the fact that something is ubiquitous is one argument in its favor—in some contexts—but shouldn’t be taken as definitive. Technologies get quietly phased out all the time.

More broadly, though, I’m not suggesting that the Nix community shift to a new builder en masse. Different builders can seamlessly interoperate in Nix and there’s plenty of room for experimentation here and no reason to be concerned about fragmentation.

4 Likes