[LINUX] Declaratively manage your environment with Nix and home-manager

Once you have your Nix package manager in place, you definitely want to have home-manager. home-manager is a tool for declaratively managing user environment using nix.

--Packages to install --Configuration files such as ~ / .profile, ~ / .bashrc, ~ / config / fish --fish, tmux, firefox, plugins --email account

It is an excellent tool that allows you to manage various user settings declaratively in a single file.

If you put only nix and home-manager, even if you switch to a PC, it is not a dream to complete the setup with a single command. (I did that when I actually replaced my PC at work.)

For more information, see home-manager's github page or manual. As you can see, it's in English and it's long, so I'll just summarize the main points for myself.

Home-manager installation

You need a Nix package manager to use home-manager, so install Nix first.

> curl -L https://nixos.org/nix/install | sh
> nix-channel --add https://nixos.org/channels/nixpkgs-unstable nixpkgs
> nix-channel --update

You can subscribe to the channel with nixchannel --add <url> [channel-alias]. It's like registering a repository.

To install home-manager, first subscribe to the home-manager channel.

> nix-channel --add https://github.com/nix-community/home-manager/archive/master.tar.gz home-manager
> nix-channel --update

Then you can install it by typing the following command.

> nix-shell '<home-manager>' -A install

Package management

To set up your user environment using home-manager, create ~ / .config / nixpkgs / home.nix.

For example, the following settings install git and GNU / hello.

nix:.config/nixpkgs/home.nix


{ pkgs, ... }:
{
    #This is home-Enters automatically when manager is installed
    programs.home-manager.enable = true;
    home.username = "hnakano";
    home.stateVersion = "20.09";

    #Declare the package to install here
    home.packages = [
        pkgs.git
        pkgs.hello
    ];
}

Then type the following command to reflect the settings.

> home-manager switch

This configuration file is written in a proprietary language called Nix expression. To be honest, it's almost like json, so you don't have to worry too much about grammar, but I will explain the minimum grammar for those who care.

Minimum grammar for using home-manager

repl There is a Nix expression repl. You can enter repl by typing nix repl on the command line.

> nix repl
Welcome to Nix version 2.3.7. Type :? for help.

nix-repl>

Type :? To get help with the repl command. You can exit the repl with : q.

Basic type

There are numeric, Boolean, and string types. The string concatenation is +.

nix-repl> 1 #interger
1

nix-repl> 3.14 #float
3.14

nix-repl> true #boolean
true

nix-repl> "hogehoge" #string
"hogehoge"

nix-repl> "hoge" + "fuga" #string concatenate
"hogefuga"

let ... in

Use let ... in to assign a value to a variable. Pay attention to the position of the semicolon.

nix-repl> let x = 1; y = 2; z = x + y; in z * 2
6

You can assign values only in repl without using let ... in. However, please note that normal Nix expression cannot be written in this way.

nix-repl> x = 3

nix-repl> y = 4

nix-repl> x + y
7

List

The list is [a b c]. Element delimiters are single-byte spaces or line breaks. By the way, the types do not need to match **

nix-repl> [ 1 "hoge" true ]
[ 1 "hoge" true ]

nix-repl> [
            1
            2
            3
          ]
[ 1 2 3 ]

To access the elements of the list, use the built-in function builtins.elemAt. No parentheses are required to apply the function.

nix-repl> x = [ 1 2 3 ]

nix-repl> builtins.elemAt x 0
1

nix-repl> builtins.elemAt x 1
2

function

Nix functions are of the form ʻarg: body. The left side of the colon is the argument. The two-argument function is ʻarg1: arg2: body.

nix-repl> hello =  x: "hello, " + x  

nix-repl> hello "world"
"hello, world"

Basically Nix can only create anonymous functions.

nix-repl> x: y: x + y
«lambda @ (string):1:1»

So if you want to name the function, use the let expression

nix-repl> let
            add = x: y: x + y;
            add2 = add 2; #Can be partially applied
          in
          add2 3 #Same as add 2 3
5

Attribute set

An Attribute set is a pair of key = value enclosed in curly braces in the form {key1 = value1; key2 = value2;}. A semicolon is always required at the bottom of the key = value; pair.

You can access the value in the form x.key1.

nix-repl> let
            x = {     
              hoge = "hoge";
              fuga = "fuga";
            };
          in x.hoge
"hoge"
A function that takes an attribute set as an argument

Functions that take an attribute set as arguments can usually be defined in the form ʻarg: body`.

nix-repl> let
            f = x: x.hoge + x.fuga;
          in f { hoge = "hoge"; fuga = "fuga"; }
"hogefuga"

However, it can also be defined in the form {key1, key2}: body instead. In this form, an error will occur if the name of the argument key does not exactly match. You can avoid this error by putting ... in the argument, like {key, key2, ...}: body.

nix-repl> f = { hoge, fuga }: hoge + fuga

nix-repl> let x = { hoge = "hoge"; fuga = "fuga"; }; in f x
"hogefuga"

nix-repl> let y = { hoge = 3; fuga = 4; piyo = true; }; in f y
error: anonymous function at (string):1:2 called with unexpected argument 'piyo', at (string):1:50

nix-repl> g = { hoge, ... }: "hello, " + hoge

nix-repl> let y = { hoge = "world"; fuga = 4; piyo = true; }; in g y
"hello, world"

Other

Nix Expression grammar reference

--Official manual https://nixos.org/manual/nix/stable/#chap-writing-nix-expressions --Nix Pills (Official Blog) https://nixos.org/guides/nix-pills/basics-of-language.html

Read the config file

Let's read the first defined configuration file again.

nix:./config/nixpkgs/home.nix


{ pkgs, ... }:
{
    #This is home-Enters automatically when manager is installed
    programs.home-manager.enable = true;
    home.username = "hnakano";
    home.stateVersion = "20.09";

    #Declare the package here
    home.packages = [
        pkgs.git
        pkgs.hello
    ];
}

First, we can see that this expression is a function that takes an Attribute set with the key pkgs as an argument. pkgs usually contains<nixpkgs>that is, the Unix package collection.

{key1.key2 = value;} is syntactic sugar for nested attribute sets, which stands for {key1 = {key2 = value;};}.

Without syntactic sugar, this config file would look like this:

{ pkgs, ... }:
{
    programs = {
        home-manager = {
            enable = true;
        };
    };

    home = {
        username = "hnakano";
        stateVersion = "20.09";
        packages = [
            pkgs.git;
            pkgs.hello;
        ];
    };
}

That is, this function takes an Attribute set of {pkgs = ..; ...} as an argument. It is a function that returns an Attribute set of {programs = {...}; home = {...};}.

The home-manager command evaluates this return value and reflects the settings in the user environment.

Other setting items

I will try various settings using home-manager. For the contents that can be set in home-manager, refer to home-manager manual.

nix:.config/nixpkgs/home.nix


{
  home.packages = with pkgs; [ nixfmt exa bat source-han-code-jp ];

  programs.git = {
    enable = true;
    userName = "hnakano863";
    userEmail = "[email protected]";
    extraConfig.pull.rebase = false;
  };

  fonts.fontconfig.enable = true;

  programs.bash = {
    enable = true;
    profileExtra = ''
      export XDG_DATA_DIRS=$HOME/.nix-profile/share''${XDG_DATA_DIRS:+:}$XDG_DATA_DIRS
      export LIBGL_ALWAYS_INDIRECT=1
      export DISPLAY=$(cat /etc/resolv.conf | grep nameserver | awk '{print $2}'):0
      if [ -e $HOME/.nix-profile/etc/profile.d/nix.sh ]; then
          . $HOME/.nix-profile/etc/profile.d/nix.sh;
      fi
    '';
    initExtra = ''
      if [ -z $IN_NIX_SHELL ]; then exec fish fi
    '';
    shellAliases = {
      ls = "exa";
      cat = "bat";
    };
  };

  programs.fish = {
    enable = true;
    interactiveShellInit = ''
      set -gx fish_user_paths $HOME/go/bin $HOME/.local/bin
    '';
  };
      
  programs.tmux = {
    enable = true;
    clock24 = true;
    keyMode = "vi";
    shortcut = "a";
    terminal = "screen-256color";
  };
}

There are some grammatical items that I have not explained, so I will explain them.

First, in home.packages, there is the formwith pkgs; [...]. This is a syntax to avoid writing pkgs., such as[pkgs.nixfmt pkgs.exa ...].

Next is the string enclosed in two single quotes that appears in programs.bash.profile Extra etc. This is a string of type indented string.

Indented is ignored in the indented string, which is useful for writing shell scripts. In fact, programs.bash.profile Extra is reflected in ~ / .profile.

Finally, if you have programs.bash.enable = true;, running home-manager switch will try to replace ~ / .profile, ~ / .bashrc, and so on. At this time, if an existing configuration file such as ~ / .bashrc already exists in the user environment, the home-manager command will fail with an error saying "I have a configuration file". To avoid this, save the existing files in advance with mv ~ / .bashrc ~ / .bashrc.bk.

Recommended Posts

Declaratively manage your environment with Nix and home-manager
Manage Python runtime packages and development environment packages with Poetry
Manage python environment with virtualenv
Manage your data with AWS RDS
Environment construction with pyenv and pyenv-virtualenv
Build a Python environment on your Mac with Anaconda and PyCharm
Clean python environment with pythonz and virtualenv
MacOS 10.11 environment construction: Powerline with Anaconda and Dein.vim
Building a python environment with virtualenv and direnv
Django: Record User Agent and manage with Admin
Manage Python multiple version environment with Pythonz, virtualenv
Manage state transitions and communicate with smart meters
Build a virtual environment with pyenv and venv
Build PyPy and Python execution environment with Docker
Make your Python environment "easy" with VS Code
Build a python virtual environment with virtualenv and virtualenvwrapper
Build a python virtual environment with virtualenv and virtualenvwrapper
Manage your Amazon CloudWatch loggroup retention with AWS Lambda
Install Ubuntu 20.04 with GUI and prepare the development environment
Automate Chrome with Python and Selenium on your Chromebook
How to set up and compile your Cython environment
Build a numerical calculation environment with pyenv and miniconda3