envil
is a tool to:
- describe a set of environments ("toolkits") that contain executables you regularly want to access and hide rapidly (see for instance here),
- install and switch on/off such environments globally in an environment stack, or conversely:
- start a subshell with a specific set of environments locally activated.
envil
does so by using Nix, and by inspecting or creating Nix flakes on the fly.
In the Nix ecosystem, a flake is a file that describes a set of programs (the flake outputs) for various possible systems,
and how to download and build those programs and their dependencies (the flake inputs). Nix can then install those programs in an isolated store,
ie. without risking potential conflicts with pre-existing tools on your system. Nix being a simple yet generic and powerful programming language,
some seemingly simple use cases (such as listing a fixed set of pre-existing packages in a flake and installing them) may appear more complex
to tackle than they need to.
envil
targets some of those simple Nix use cases. It aims at providing people who are not regular Nix users a quick way to start with custom,
isolated & reproducible (ie. "rebuildable identically elsewhere") environments, one of the major reasons to use Nix.
It is important to note that envil
is not a tool to:
- install and manage Nix for you,
- write complicated Nix logic for you.
If you already know and use Nix, envil
aims at being an alternative to the nix profile
command which makes it inconvenient to work with
multiple profiles and contributes to cluttering your PATH. Conversely, envil
enables you and incites you to be selective and to
quickly switch between environments or start shells to avoid situations where you end up with two different versions
of the same tool in your PATH
, or things like two different python
installations but each one configured with its own libraries,
leaving you unable to select which one you want.
envil
has been developped with cooperation between Nix-users and non Nix-users in mind, notably in development teams where some people would
want to introduce Nix to provision their local development environment(s) without too much friction.
This is why envil
introduces a simple and versionable yaml environment description, but also directly supports any flake as a package source,
and also why it operates by outputting regular Nix flakes that are locked and versionable too.
To install envil
, first you need to install Nix. Then just do:
nix profile install github:YPares/envil#envil
to have envil
available in your PATH
. Alternatively, you can run nix run github:YPares/envil
everytime you want to use envil
.
Finally, add $HOME/.envil/current/bin
to your PATH
. This is the directory in which envil will install and swap the binaries of your
current environment.
Note: if you are using Linux, it's better to set your PATH
in your $HOME/.pam_environment
or $HOME/.profile
so other programs than your terminal can see the updated PATH
.
Don't forget to log out and back in after.
envil
manages environments as a stack. That means several envs can be activated at the same time. The main commands
are envil push
and envil pop
to add or remove envs from the stack, and envil show
to view the stack's current state.
The second important concept is the notion of statedir. That is some directory that contains a configuration file that describes the desired state of your environments. This file can be one of the following two:
- either an
envil-state.yaml
("yaml statedir"):envil
will generate a flake for each environment inside the statedir (in theflakes
subfolder). Each env has its own flake, so envs are locked separately. - or a
flake.nix
("flake statedir"):envil
will look at the attributes exported underpackages.<current_system>
and consider each one to be an environment. All the envs are in the same flake, so envs are locked together.
Therefore any regular Nix flake is directly usable as a statedir, and in such case envil
will use it as is, in a readonly
fashion, so it's up to you to write it and update its lockfile as you see fit. This allows to use remote flakes too (e.g. from Github)
without having to pre-clone them locally. On the other hand, yaml statedirs must be local, writeable directories.
Note that if a statedir contains both files, envil
will use the flake.nix
and ignore the envil-state.yaml
.
Most envil
subcommands take a -d
argument to select which statedir to operate with (regardless of whether it is a yaml or flake statedir),
and register it as the current statedir so you don't have to repeat it everytime.
If the folder given to -d
does not exist or is empty, envil
will create it with a default envil-state.yaml
that you can then edit.
Each environment present in your stack can come from a different statedir, thus allowing decomposition. You can for instance
have your own personal statedir for tools only you use, install tools from public flakes on Github, and then have another statedir that is part of a git project
and used by all the developers of that project. Nix flake.lock
files ensure that all teammembers will use the exact same version
of the same tools, and Nixpkgs buildEnv
function will make sure you do not have conflicts (different executables with the same name) in your stack.
Run envil -h
to see all the commands available. For instance, if you clone that repository and cd
into your local clone,
you can run the following:
envil shell -d examples/statedir
: show all the envs defined in the example statedir (-d
) and select some of them. Then open those envs in a subshell. Registerexamples/statedir
as the current statedirenvil switch -d examples/statedir
: select several environments in the statedir, and then replace the whole stack with them. Registerexamples/statedir
as the current statedir.envil push
: select an env from the current statedir (no-d
was given) to add to the top of the env stack. Globally add to yourPATH
the executables it containsenvil pop
: remove the last env added to the stackenvil toggle
: toggle on/off some environments in the stack (same effect than push/pop, but it's easier to reactivate them afterwards)envil show
(or justenvil
): show the current statedir, the envs in the stack, the bins in the PATH, and the envs activated in the current subshell (if any). The source (statedir of origin) of each environment is also indicated, with a flake URL if the statedir is a Nix flake, and a regular absolute path if it is a yaml statedirenvil update
: update the flake.lock files for some environments in the current statedir. This is only for yaml statedirs. This is the command to use if you want to generate the flakes for a yaml statedir ahead of time, without activating any envenvil switch -r
: reload the current stack (for instance if you usedenvil update
previously)
Subshells started by envil
export the $SHELL_ENV
environment variable. You can use it in your shell prompt (eg. PS1
for bash
) so it shows
which env(s) is (are) activated in the subshell. For instance if you use bash
, add the following to your .bashrc
:
if [[ -n "$SHELL_ENV" || "$SHLVL" > 1 ]]; then
shell_env_bit='\e[0;33m[$SHELL_ENV($SHLVL)]\e[0m'
fi
PS1="${shell_env_bit}...the rest of your prompt..."
($SHLVL
is a standard bash
environment variable telling you how many levels of subshells you are currently in)
Run nix profile upgrade envil --refresh
to update envil
to the latest version.
If you use cachix
and if you set up a cache on https://www.cachix.org, run:
cachix push <cache_name> $HOME/.envil/current
Anybody who runs cachix use <cache_name>
and switches to the same env(s) than you will benefit from the binary cache.
If you are using an external store as a binary cache, you can use nix copy
:
nix copy $HOME/.envil/current --to "<cache_url>"
In each statedir containing an envil-state.yaml
, envil
will generate a flakes
folder, itself containing a flake.nix
and flake.lock
for each env.
These are meant to be added to your version control system, so that any person using these envs too will have the exact
same tool versions you do.
Conversely, the contents of your $HOME/.envil
are not meant to be versioned. They represent the state of your own stack,
which is meant to be transient. If you often end up activating the exact same set of envs, you can declare a new env
in your statedir that will extend from them, ie. merging them all together, to make things more convenient.
Statedirs can also include one another to create envs that extend from envs from different statedirs.
You have two possible workflows with envil
:
- Decomposition: smaller and separated envs, with frequent use of
envil push
andpop
- Composition: bigger environments that extend one another, with frequent use of
envil switch
envil
is related to niv
, devenv
, devbox
,
flox
, flakey-profile
and
home-manager
but with a focus on:
- usability by people who do not write or write little Nix code;
- compatibility with existing Nix tools, and no disruption of your existing Nix installation and configuration;
- reusable and composable environments, meaning that:
- any env can extend (or import, include, whatever you prefer) other envs,
- statedirs can be imported and included into one another,
- you can have several environments activated at the same time;
- production of regular and (as much as possible) idiomatic Nix flakes that do not require
--impure
.
Also, envil
strongly encourages decomposition. If you write Nix code, then writing small & local Nix flakes to
then reuse them in envil
envs is perfectly encouraged. envil
will not write complicated Nix logic for you,
just the classic boilerplate needed to define a top-level flake with some pkgs.buildEnv
calls.
Contrary to nix profile
, envil
will not do anything to track versions of environments via a history.
Given it represents its configuration as a simple yaml file or as flakes, versioning can just be done with git
.