diff --git a/source/tutorials/index.md b/source/tutorials/index.md index e244be501..a5f93a1bc 100644 --- a/source/tutorials/index.md +++ b/source/tutorials/index.md @@ -9,6 +9,7 @@ These sections contains series of lessons to get started. install-nix.md first-steps/index.md +learning-journey/index.md nixos/index.md cross-compilation.md ``` diff --git a/source/tutorials/learning-journey/index.md b/source/tutorials/learning-journey/index.md new file mode 100644 index 000000000..d7778ea18 --- /dev/null +++ b/source/tutorials/learning-journey/index.md @@ -0,0 +1,10 @@ +# Learning Journey + +This collection of tutorials guides you through your first steps with Nix. +This series is a work in progress and will have some overlap with existing tutorials. +The intention is to unify these tutorials over time. + + ```{toctree} + :maxdepth: 1 + shell-dot-nix.md + ``` diff --git a/source/tutorials/learning-journey/shell-dot-nix.md b/source/tutorials/learning-journey/shell-dot-nix.md new file mode 100644 index 000000000..606066978 --- /dev/null +++ b/source/tutorials/learning-journey/shell-dot-nix.md @@ -0,0 +1,152 @@ +# Creating shell environments + + + +## Overview + +### What will you learn? + + +How to create and configure reproducible shell environments + +### How long will it take? +30 minutes + + + +### What will you need? + + +A basic understanding of the Nix language + +## Entering a shell with Python installed +Suppose we wanted to enter a shell in which Python 3 was installed. +The simplest possible way to accomplish this is via the `nix-shell -p` command: +``` +$ nix-shell -p python3 +``` + +This command works, but there's a number of inefficiences: +- You have to type out `-p python3` every time you enter the shell. +- It doesn't scale to an arbitrary number of packages (you would have to type out each package name each time). +- It doesn't (ergonomically) allow you any further customization of your shell environment. + +A better solution is to create our shell environment from a `shell.nix` file. + +## A basic `shell.nix` file +The `nix-shell` command by default looks for a file called `shell.nix` in the current directory and tries to build a shell environment by evaluating the Nix expression in this file. +So, if you properly describe the shell environment you want in a `shell.nix` file, you can enter it with just the `nix-shell` command without any further arguments. +No more specifying packages on the command line. +Here's what a basic `shell.nix` looks like that installs Python 3.10 as before: +```nix +let + pkgs = import {}; +in + pkgs.mkShell { + packages = [ + pkgs.python3 + ]; + } +``` +where `mkShell` is a function that when called produces a shell environment. + +If you save this into a file called `shell.nix` and call `nix-shell` in the directory containing this `shell.nix` file, you'll enter a shell with Python 3 installed. + +## Adding packages +Additional executable packages are added to the shell by adding them to the `packages` attribute. +For example, let's say we wanted to add `curl` to our shell environment. +The new `shell.nix` would look like this: +```nix +let + pkgs = import {}; +in + pkgs.mkShell { + packages = [ + pkgs.python3 + pkgs.curl # new package + ]; + } +``` + +:::{note} +`nix-shell` was originally conceived as a way to construct a shell environment containing the tools needed to *develop software*; only later was it widely used as a general way to construct temporary environments for other purposes. Also note that `mkShell` is a [wrapper around `mkDerivation`](https://nixos.org/manual/nixpkgs/stable/#sec-pkgs-mkShell) so strictly speaking you can provide any attributes to `mkShell` that you could to `mkDerivation` such as `buildInputs`. However, the `packages` attribute provided to `mkShell` is an alias for `buildInputs`, so you shouldn't need to provide both `packages` and `buildInputs`. +::: + +## Environment variables +It's common to want to automatically export certain environment variables when you enter a shell environment. +For example, you could have a database that depends on an environment variable to set the default authentication credentials during development. + +Setting an environment variable in via `shell.nix` is trivial. +Any attribute in the `mkShell` function call that `mkShell` doesn't recognize as a reserved attribute name will be set to an environment variable in the shell environment. +The attributes that are reserved are listed in the [Nixpkgs manual][mkshell_attrs] and include `packages`, `name`, and several others. + +[mkshell_attrs]: https://nixos.org/manual/nixpkgs/stable/#sec-pkgs-mkShell-attributes + +Let's say you wanted to set the database user (`DB_USER`) and password (`DB_PASSWORD`) via environment variables in your `shell.nix` file. +This is how that would look: +```nix +let + pkgs = import {}; +in + pkgs.mkShell { + packages = [ + pkgs.python310 + pkgs.curl + ]; + + env = { + # Database credentials + DB_USER = "db_user"; + DB_PASSWORD = "super secret don't look"; + }; + } +``` + +:::{warning} +Some variables are protected from being overridden via the `env` attribute as described above. + +For example, the shell prompt format for most shells is set by the `PS1` environment variable, but `nix-shell` already overrides this by default, and will ignore a `PS1` attribute listed in `env`. + +If you _really_ need to override these protected environment variables you can use the `shellHook` feature discussed in the next section and `export MYVAR="value"` in the hook script. +It's generally discouraged to set environment variables this way. +::: + + +## Startup commands +You may want to perform some initialization before entering the shell environment (for example, maybe you want to ensure that a file exists). +Commands you'd like to run before entering the shell environment can be placed in the `shellHook` attribute of the attribute set provided to the `mkShell` function. +To ensure that a file `should_exist.txt` exists, the `shell.nix` file would look like this: + +```nix +let + pkgs = import {}; +in + pkgs.mkShell { + packages = [ + pkgs.python310 + pkgs.curl + ]; + + env = { + # Database credentials + DB_USER = "db_user"; + DB_PASSWORD = "super secret don't look"; + }; + + # Set shell prompt format, ensure that 'should_exist.txt' exists + shellHook = '' + export PS1="\u@\h >>> " + touch should_exist.txt + ''; + } +``` + +Some other common use cases for `shellHook` are: +- Initializing a local data directory for a database used in a development environment +- Running commands to load secrets into environment variables +- Installing pre-commit-hooks + +## Where to next? +- [`mkShell` documentation](https://nixos.org/manual/nixpkgs/stable/#sec-pkgs-mkShell) +- Nixpkgs [shell functions and utilities](https://nixos.org/manual/nixpkgs/stable/#ssec-stdenv-functions) documentation +