Question about code/initialization approach

Hi! I’ve been learning Nix and implementing some of my favourite programs as nix derivations/modules

One of the programs I use a lot (and many of my workplace’s codebases rely on) is NVM, which injects itself into the shell and manages node versions.

It manages a folder called .nvm in the home directory, so I need to initialize that folder and install a default node version when this module is applied.

So my question is, is the below code the best approach to doing that? I need code that runs only once to initialize a mutable directory which will then be handled by the program afterwards. I kept as much immutable as possible, e.g. nvm.sh gets symlinked in.

I symlinked nvm.sh manually rather than using home manager’s primitives because I believe those get executed before the activation step, and I need activation to create the folder first.

  home.activation = {
    initializeNvm = lib.hm.dag.entryAfter [ "writeBoundary" ] ''
      # If the ~/.nvm directory exists, then we assume that nvm has already been
      # installed and activated. Otherwise, we activate it.

      if [ -d $HOME/.nvm ];
      then
        echo "nvm already initialized"

        # Delete the nvm.sh file and symlink the new one
        $DRY_RUN_CMD rm -f $HOME/.nvm/nvm.sh
        $DRY_RUN_CMD ln -s ${nvm}/nvm.sh $HOME/.nvm/nvm.sh
      else
        echo "Initializing nvm"
        $DRY_RUN_CMD mkdir -p $HOME/.nvm

        # Symlink in nvm.sh
        $DRY_RUN_CMD ln -s ${nvm}/nvm.sh $HOME/.nvm/nvm.sh

        # Add required packages to the PATH for nvm to work
        $DRY_RUN_CMD export PATH=$PATH:${pkgs.curl}/bin:${pkgs.gawk}/bin:${pkgs.gnutar}/bin:${pkgs.gzip}/bin

        $DRY_RUN_CMD source $HOME/.nvm/nvm.sh
        $DRY_RUN_CMD nvm install 20
      fi
    '';
  };

My end goal is to make sure this follows all best practices, and integrate it with all shells that nvm supports, and hopefully PR it into nixpkgs.

But yeah, am I approaching this right or completely missing the point?

Edit: I noticed I can get rid of the symlinking step by changing some environment variables. However, assuming I couldn’t do that, is the above best practice still given the constraints I mentioned?

Are you really really really dependent on NVM? If so in which way exactly? Because typically what I would do with any of these tools is replace them with a nix-shell or devShell that provides the necessary nodejs binary.

However since you are getting creative here, I’d also love to see where this goes tbh.

1 Like

It would be nice to keep nvm, as I have many repositories with many different versions of node, and a .nvmrc file in each that tells my IDE what version of node to use. I can probably hack my own script that does the same though… But also I’m here to learn about best practices anyway, so I might as well see this through.

I am not that great with nix yet so as to tell you what is really best practice. But what you can do is get the nodejs version from .nvmrc into a nix-shell like so: nodejsVersion = lib.fileContents ./.nvmrc; The fileContents function allows you to just read it in.

More details can be the found in the otherwise somewhat chaotic NixOS Wiki. You will find the same line there: Node.js - NixOS Wiki

You would then use Nix to build a shell with that specific nodejs version. I personally would reach for flakes here and make a devShell via nix develop. But that is just personal preference. And if you run your IDE from inside the shell, it should pick it up.

The big downside to this would be that you will most likely have to compile nodejs to the get the shell. Unless you hack something together that can get the cached major nodejs version from nixpkgs instead, if available.

My end goal is to make sure this follows all best practices, and integrate it with all shells that nvm supports, and hopefully PR it into nixpkgs.

All the things above being said: If this end goal is achievable, it would be great.

1 Like

Yeah thank you, I’m still trying to fully understand the right way to approach nix dev shells (e.g. they all default to bash, but most nix power users don’t use bash I presume, so there’s third party projects trying to fix it), but also how it all could integrate with my IDE which is vscode (yes I know people shit on vscode, but it’s for work reasons), and whether it requires adding extra files to the repo that other devs wouldn’t want to bloat up the repo root folder.

But yeah anyway what you pointed to is pretty helpful, I’ll keep playing with the different options until I find the best workflow for me

Yes, and let us know the process here! Because as you move along you encounter problems. And I believe there are certainly some people here, including in the core team, that are really interested in these user adoption stories.

Because as you already noticed, nix has a learning curve. And in addition to that it has quirks. Like the role that the bash shell plays. And then there is the problem that most software is not designed for nix specifically. So you are essentially working around problems in order to accommodate assumptions the software makes.

how it all could integrate with my IDE which is vscode

I also use VSCode. (I also use Neovim, but not that much actually) I manage the base config for VSCode via home-manager. Including many plugins which you can get in nixpkgs.

When it comes to devShells or nix-shell you can run vscode from within the shell they provide. To me that is the most straightforward approach for using the specific environment that the devShell / nix-shell provides. When I do that with devShell I make sure that bashInteractive is in my buildInputs or nativeBuildInputs for the devShell. I had strange issues with VSCode otherwise.

In my case, it’s actually not feasible to purely rely on command line for starting vscode, I can try to check if there’s extensions that initialize a nix environment for the current window, but I need vscode to still work over SSH.

About 50% of the time I use vscode over remote SSH from my workplace to my home computer, the rest of the time I’m on my computer directly. I don’t think there’s a built in way to open vscode SSH within a custom shell. I’ll need to keep testing of course though…

1 Like

Yes that is an important use case! I am not entirely sure how to solve it.

But you may want to look at the Nix Environment Selector and Nix IDE extentions for VSCode. Maybe they can help:

1 Like