Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add shell.nix tutorial #671

Merged
merged 3 commits into from
Aug 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions source/tutorials/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
```
10 changes: 10 additions & 0 deletions source/tutorials/learning-journey/index.md
Original file line number Diff line number Diff line change
@@ -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
```
152 changes: 152 additions & 0 deletions source/tutorials/learning-journey/shell-dot-nix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
# Creating shell environments

<!-- Include any foreward you want here -->

## Overview

### What will you learn?

<!-- Give a brief description of what the reader will learn so that they know whether the topic interests them. -->
How to create and configure reproducible shell environments

### How long will it take?
30 minutes

<!-- Give some indication of how long it will take to complete the tutorial so that the reader knows whether to continue. -->

### What will you need?

<!-- List any prerequisite knowledge or tools the reader will need to complete the tutorial. -->
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:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Here's what a basic `shell.nix` looks like that installs Python 3.10 as before:
Here's what a basic `shell.nix` looks like that installs Python 3 as before:

```nix
let
pkgs = import <nixpkgs> {};
in
pkgs.mkShell {
packages = [
pkgs.python3
];
}
```
zmitchell marked this conversation as resolved.
Show resolved Hide resolved
where `mkShell` is a function that when called produces a shell environment.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
where `mkShell` is a function that when called produces a shell environment.
where `mkShell` is a function that 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 <nixpkgs> {};
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`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:::

## 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 <nixpkgs> {};
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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Discussed online: mention this as a warning rather than guidance. Mention that you can use shellHook to still set these variables if you need to. Then illustrate the Bash syntax for exporting environment variables to do this as a one line example. Another example is setting secrets you get from another command.

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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It shouldn't be called discouraged, it's very valid for secret environment variables.

:::

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Other variables are protected: the shell prompt format is set by the `PS1` environment variable, but `nix-shell` already overrides this by default, and does not allow us to alter the `PS1` attribute directly. In these cases, we can still set a new value for that environment variable by manually exporting it in the [`shellHook` attribute](https://nixos.org/manual/nixpkgs/stable/#sec-pkgs-mkShell-attributes) passed to `mkShell`.
To set the shell prompt to the format `<username>@<hostname> [myEnv] $ `, the `shell.nix` file would look like this:
```nix
let
pkgs = import <nixpkgs> {};
in
pkgs.mkShell {
packages = [
pkgs.python310
pkgs.curl
];
# Database username and password
DB_USER = "db_user";
DB_PASSWORD = "super secret don't look";
# Set the shell prompt to '<username>@<hostname> [myEnv] $ '
mkShell = ''
export PS1="\u@\h [myEnv] $ ";
'';
}


## 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:
zmitchell marked this conversation as resolved.
Show resolved Hide resolved

```nix
let
pkgs = import <nixpkgs> {};
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
'';
}
```
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Discussed online: Provide other examples such as initializing a data directory for a database, getting secrets, etc.


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