Run some bash script when flake is initialized

Hi! I’m trying to learn to make development flake (through the mkShell function). I am working on a luajit-luarocks flake, in which I’d like to initialize a bunch of stuff like git repo, luarocks virtualenv, direnv, etc…

I could not find any resource to run some script only when the flake is initialized (if I understand, shellHook runs every time I enter the developement environment, and I don’t want to git init every time I enter the project).

Here is what I have so far:

{
  description = "A LuaJIT app development flake";
  inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
  inputs.systems.url = "github:nix-systems/default";
  inputs.flake-utils = {
    url = "github:numtide/flake-utils";
    inputs.systems.follows = "systems";
  };

  outputs =
    { nixpkgs, flake-utils, ... }:
    flake-utils.lib.eachDefaultSystem (
      system:
      let
        pkgs = nixpkgs.legacyPackages.${system};
        project_name = ""; # Enter project name here
      in
      {
        devShells.default = pkgs.mkShell {
          inputsFrom = with pkgs; [
            luajitPackages.luarocks
            luajit
          ];

	  buildInputs = [
	    pkgs.stdenv.mkDerivation {
              name = "prepare";
              src = ./.;
	      installPhase = ''
                project_name=${project_name}
                if [ "$project_name" = "" ]; then
                  project_name="$(basename "$PWD")"
                fi
              
                # Note: this block must come before the luarocks init
                # Luarocks will adapt the generated config from this
                mv "$PWD/lua/project-name" "$PWD/lua/$project_name"
              
                luarocks init --wrapper-dir="$PWD/bin" --lua-versions=5.1 --no-gitignore "$project_name"
                cp "$PWD/bin/lua" "$PWD/bin/luajit"
              
                git init
                git add "$PWD/flake.nix"
              
	        direnv allow "$PWD"
	     '';
            }
	  ];

          shellHook = ''
            # Put the local bin in front to give it priority
            export PATH="$PWD/bin:$PATH"
          '';
        };
      }
    );
}

The installPhase part isn’t ran. Same if I put it in mkShell’s top level or if I replace it with buildPhase. Any idea? Thanks in advance.

What does “initializing” the flake mean for you? Does entering the directory for the first time qualify? If so, use direnv to run some shell code.

I could try with direnv I guess.

By initializing, I just mean nix flake new/init -t what#ever [target-directory]. I would like something to run at this moment (or the first time I call nix develop inside), not just everytime I enter the flake’s shell.

Add a shellHook long string to your mkShell function and that is a bash script that runs on shell activation.

As I explained in the question, what I want is not a shellHook (which I already have as you can see) but a script that only runs on initialization.

Sorry about that, I glossed too quickly over the thread. Yes, it would run each and every time.

You could utilize your prepare, though you’d have to call it. Adding a logic check for .git or other artifact presence could exit the prepare, on the assumption that the init is completed already. These changes might be helpful:

{
  description = "A LuaJIT app development flake";
  inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
  inputs.systems.url = "github:nix-systems/default";
  inputs.flake-utils = {
    url = "github:numtide/flake-utils";
    inputs.systems.follows = "systems";
  };

  outputs = {
    nixpkgs,
    flake-utils,
    ...
  }:
    flake-utils.lib.eachDefaultSystem (
      system: let
        pkgs = nixpkgs.legacyPackages.${system};
        project_name = ""; # Enter project name here
      in {
        devShells.default = pkgs.mkShell {
          inputsFrom = with pkgs; [
            luajitPackages.luarocks
            luajit
          ];

          buildInputs = [
            (pkgs.writeShellApplication
            {
              name = "prepare";
              checkPhase = "";
              runtimeInputs = [pkgs.git pkgs.direnv];
              text = ''
                 # Init will not run if git already exists
                 if [ -d .git ]; then
                   exit 0
                 fi

                 project_name=${project_name}
                 if [ "$project_name" = "" ]; then
                   project_name="$(basename "$PWD")"
                 fi

                 # Note: this block must come before the luarocks init
                 # Luarocks will adapt the generated config from this
                 mv "$PWD/lua/project-name" "$PWD/lua/$project_name"

                 luarocks init --wrapper-dir="$PWD/bin" --lua-versions=5.1 --no-gitignore "$project_name"
                 cp "$PWD/bin/lua" "$PWD/bin/luajit"

                 git init
                 git add "$PWD/flake.nix"

                direnv allow "$PWD"
              '';
            })
          ];

          shellHook = ''
            # Put the local bin in front to give it priority
            export PATH="$PWD/bin:$PATH"
            
            # Run the preparation script
            prepare
          '';
        };
      }
    );
}

The install was converted to a shell application that is now called by the shell hook, it either inits your script or quits early if the git repo already existed.


Direnv note for other projects:

Lastly, I use layout from direnv to accomplish automatic handling of venv. The benefit is that it automatically handles and integrates that directly into the environment and caches it. However, I don’t believe there’s a custom implementation for arbitrary triggers. More info from direnv’s lib: DIRENV-STDLIB 1 “2019” direnv “User Manuals” | direnv

Thanks for the inputs. I actually tried that .git heuristic but it seems uncomfortable. So far, I feel like I’m better off with a wrapper bash script that also calls nix flake new.

That layout command looks great, but there is no support for Lua in there :person_shrugging:

1 Like

This isn’t possible and I reckon security is one of the main reasons why. It would be an absolute nightmare security wise if a template could just run arbitrary shell code.

Templates expose an attribute called welcomeText that prints to the console when a template is first initialized. You could maybe have it print out the shell commands that you want to run, then copy paste them to your shell.

Alternatively, if you go down the direnv route, you can have it make a file that acts like a “lock”, preventing whatever one time changes from running again.