Kami level power? Take my root and show me the code...

Nix is More Powerful Pkg Manger!

In pkg managing universe, if Conda is miniverse, Nix would be, let's take one step further, perhaps the real universe, or the last step before entering hybrid simulation world like docker etc. In package management and dev env setup, Nix is already system-level (God/Kami) isolation for *nix system! Though it doesn't isolate process and memory like docker does, which is still in my boundary of package manager. Setting up reproducible, reliable and efficient dev environment is what it can do best. Conda and others cannot do that because many pkg manager's dependencies are runtime and sensitive to envs and paths.

Want to be God-Level Goku?
Want to be God-Level Goku?

Note the price to pay: root permission, but you can do without permission if namespace supported or rebuild everything from scratch if you set store in different location than /nix/store. For linux, you have to use NixOS to enable the full power manipulating hardware and services.

Features Summary

Immutable Dependencies

Means it freezes the deps during build, and it hardcodes the tool-chain (e.g. GCC) to ignore any global system wide variable (/usr/include) and version of lib. Therefore, it can handle many different version independently for even the same app and make your life easier.

Versions

When you install, Nix uses Sqlite to persist your version and prepares for rolling back! It also creates symlink as in:

$HOME/.nix-profile/

Because of hardcoding, rolling back or version control is blasting fast just similar to how git hand files.

Easy Going as Other Managers

If you are familar with other manager tool, interacting with nix user environment is similar, e.g.:

Install -> nix-env -i pkgname
Uninstall -> nix-env -e pkgname
List -> nix-env -q

Easy Share

You can create your own environment .nix and share it easily to your colleagues. You can also build it to docker or virtual hard drive image!

Nix Expression & Language

It adapts functional programming so everything is an expression/lambda/function and variables are immutable. You can use nix repl to play with language.

You can compile expression to create derivatives which describe how to build a package. .nix is like .c in C; while .drv is like .o. Many people are scared by it?

Build Your Own Pkgs

It's all about writing a .nix expression. Find a nice example in pkgs and copy/paste!

If you want to know the configs available in home-manager, just look into code.

Install Nix

It's very easy to follow here if you have root. Otherwise, you either need namespace and do:

wget https://github.com/nix-community/nix-user-chroot/releases/download/1.0.3/nix-user-chroot-bin-1.0.3-x86_64-unknown-linux-musl -O nix-user-chroot
chmod +x nix-user-chroot
mkdir -m 0755 ~/.nix
./nix-user-chroot ~/.nix bash -c "curl -L https://nixos.org/nix/install | bash"

After you can run nix-user-chroot ~/.nix bash to start bash where /nix is mounted with namespace. You might also want to put nix-user-chroot to your $PATH.

You can play with nix-env -i to install any packages.

Install Home-Manager

You can define your nix environment and quickly switch it.

# install home-manager
nix-channel --add https://github.com/nix-community/home-manager/archive/master.tar.gz home-manager
nix-channel --update
nix-shell '<home-manager>' -A install

It's must-have if you like Nix to manage your dotfiles and home folder etc.

Setup Pkgs At Home

Here's I did some env var trick to switch different profiles, like "base", etc. to point to a cli.nix file that contains the actual code of pkgs:


{ config, pkgs, ... }:

let profiles = {
# Base includes shell and utility related install
  base = [
    ./profiles/cli.nix
  ];
  ...
};
envProfile = builtins.getEnv "MY_NIX_PROFILE";
# I'm sure here can do better, now tolerate nix baby language 😂
profile = if ("${envProfile}" == "") then "base" else "${envProfile}" ;
in
{
  nixpkgs.config.allowUnfree = true;
  # Let Home Manager install and manage itself.
  programs.home-manager.enable = true;

  imports = if (builtins.hasAttr "${profile}" profiles) then profiles."${profile}" else [ "${profile}" ];

  #
  home.username = "Zeng Dai";
  home.homeDirectory = builtins.getEnv "HOME";
  
  # Starting ver
  home.stateVersion = "20.09";
}

Then in cli.nix for "base" profile, it mainly install the CLI tools (my precious pick):

{ config, lib, pkgs, ... }:
let
  comma = import ( pkgs.fetchFromGitHub {
      owner = "Shopify";
      repo = "comma";
      rev = "4a62ec17e20ce0e738a8e5126b4298a73903b468";
      sha256 = "0n5a3rnv9qnnsrl76kpi6dmaxmwj1mpdd2g0b4n1wfimqfaz6gi1";
  }) {};
in
{
  home.packages = with pkgs; [
    fzf
    vim
    direnv # how to setup nix-shell? https://nixos.org/guides/declarative-and-reproducible-developer-environments.html#declarative-reproducible-envs
    httpie
    tmux
    ag
    fasd
    ripgrep
    tree
    broot
    ranger
    trash-cli
    rsync
    fd
    exa
    comma
    bat
    htop
    navi
    tldr
    neofetch
    tokei
  ];
}

Customization Home

How about the dotfiles? The Nix language provides tons of flexibility to do whatever you need to do. Mostly you can check the options of customization, for example.

Basically, it allows you to put all dotfiles into the nix scripts.

Desktop App

For Linux, you simply install it in Nix. To make it appear in your e.g. Gnome launchpad, you need to create .profile like:

# Makesure gnome know these path
export NIX_HOME_PATH=$HOME/.nix-profile
if [ -e $NIX_HOME_PATH/etc/profile.d/nix.sh ]; then . $NIX_HOME_PATH/etc/profile.d/nix.sh; fi # added by Nix installer

export XDG_DATA_DIRS=$HOME/.nix-profile/share:$HOME/.share:"${XDG_DATA_DIRS:-/usr/local/share/:/usr/share/}"

Logout and in to restart the desktop.

For some application, it requires GL e.g. alacritty. You need to install nixGL:

let
  nixGL = import ( pkgs.fetchFromGitHub {
      owner = "guibou";
      repo = "nixGL";
      rev = "fad15ba09de65fc58052df84b9f68fbc088e5e7c";
      sha256 = "1wc5gfj5ymgm4gxx5pz4lkqp5vxqdk2njlbnrc1kmailgzj6f75h";
  }) {};
in
{
  home.packages = with pkgs; [
    ...
    nixGL.nixGLDefault
    ...
  ];
}

Then run your GL application as nixGL alacritty.

HOWEVER, desktop support for non NixOS is limited. Basically you need many knowledge regarding your X window environment and build many things from scratch, while only NixOS has native support. If you are interested how I achieved that under Centos, please check out my nix files and .profile. Each X window desktop have its own implementation that not necessarily follows statndard. You can enable home-manager's xserver support but without NixOS, you have to know and hookup everything on your own. On Mac most app e.g. firefox won't be available, you have to write some wrapper to download/unpack dmg to cheat. At least you don't need to stick to brew cask.

Systemd Service

If you want to start daemon service in Linux, for example, a cloud folder, you can use home-manager to setup these service like this:

  systemd.user = {
    services.dropbox_rclone = let
        mountdir="${config.home.homeDirectory}/cloud/dropbox";
      in
      {
      Unit = {
        Description = "dropbox rclone mount";
      };
      Install.WantedBy = [ "multi-user.target" ];
      Service = {
          ExecStartPre = "${pkgs.coreutils}/bin/mkdir -p ${mountdir}";
          ExecStart = ''
              ${pkgs.rclone}/bin/rclone mount dropbox: ${mountdir}
          '';
          ExecStop = "${pkgs.fuse}/bin/fusermount -uz ${mountdir}";
          Type = "notify";
          Restart = "always";
          RestartSec = "10s";
      };
    };

Development

This is the area that Nix really shines. With Nix you basically could dump all other package managers. For example, you could nail down the particular version of main source nixpkgs like:

{ pkgs ? import (fetchTarball "https://github.com/NixOS/nixpkgs/archive/3590f02e7d5760e52072c1a729ee2250b5560746.tar.gz") {}
}:
{
    ...
}

to pin a specific python package:

{ pkgs ? import<nixpkgs>{} }:

let
  myranger = pkgs.python3.pkgs.buildPythonPackage rec {
    pname = "ranger-fm";
    version = "1.9.0";

    src = pkgs.python3.pkgs.fetchPypi {
      inherit pname version;
      #format = "wheel"; # tar.gz is default
      # python = "cp38";
      # abi = "cp38";
      # platform = "manylinux1_x86_64";
      sha256 = "1ay8lhgkqzmvavinbmzdgh8pgkddpx72pygi4d9ac01jbbggibkg";
    };

    #format = "wheel"; # tar.gz is default

    meta = {
      homepage = "...";
      description = "...";
    };
  };
  pythonEnv = pkgs.python3.withPackages (ps: [
    myranger
  ]);
in pkgs.mkShell {
  buildInputs = [
    pythonEnv
  ];
}

For general packages, you can always take a look at the default.nix for the package you want to pin.

Plus the power of direnv and .envrc you can do auto shell env change when enter the folder!

NixOS

Under NixOS, this idea is pushed further. Everything in OS is configurable in Nix language. I like the idea of using language to define your machine in a uniform way, but unfortunately so far only NixOS is supported. It means not transferrable to other OS. Probably I'll try it out in the future post.

...