Skip to content

Instantly share code, notes, and snippets.

@lheckemann
Last active March 11, 2024 13:11
Show Gist options
  • Save lheckemann/402e61e8e53f136f239ecd8c17ab1deb to your computer and use it in GitHub Desktop.
Save lheckemann/402e61e8e53f136f239ecd8c17ab1deb to your computer and use it in GitHub Desktop.
Expression for a buildEnv-based declarative user environment.

Expression for a buildEnv-based declarative user environment

This is one way of managing your user profile declaratively.

Alternatives include:

  • an attrset-based nix-env-based environment, installed using nix-env -ir rather than nix-env --set. LnL has an overlay which shows a way of doing this.
  • home-manager, which provides NixOS-like config for your $HOME

Note that this is incompatible with regular imperative use of nix-env, e.g. nix-env -iA nixpkgs.hello. It has the advantage of allowing the installation of multiple outputs of the same package much better than nix-env's builtin profile builder does.

I personally currently use home-manager.

Feature comparison:

Prevents imperative use of nix-env (nix-env -iA nixpkgs.hello)

  • buildEnv ✔️
    • buildEnv takes responsibility for the entire user profile, meaning that nix's builtin env builder cannot modify it
  • attrset ❌
    • the attrset approach leaves building the profile up to nix-env, which allows adding packages ad-hoc (though they will be removed on the next profile build) using nix-env -i
  • home-manager ❌
    • home-manager installs a single buildEnv into the user profile, remaining compatible with imperative/impure nix-env

Management of files outside ~/.nix-profile

  • buildEnv ❌
  • attrset ❌
  • home-manager ✔️

Declarative config like NixOS

  • buildEnv ❌
  • attrset ❌
  • home-manager ✔️

Gives good control over outputs installed

  • buildEnv ✔️
  • attrset ❌
  • home-manager ❓
/*
Save this file to ~/default.nix or your preferred path (make sure to
change the location in the update-profile script if you choose a
different place).
Install using `nix-env -f ~ --set`, from then on use `update-profile`.
*/
{ pkgs ? import <nixpkgs> {}
, name ? "user-env"
}: with pkgs;
buildEnv {
inherit name;
extraOutputsToInstall = ["out" "bin" "lib"];
paths = [
nix # If not on NixOS, this is important!
icewm
pavucontrol
redshift
firefox
(writeScriptBin "update-profile" ''
#!${stdenv.shell}
nix-env --set -f ~ --argstr name "$(whoami)-user-env-$(date -I)"
'')
# Manifest to make sure imperative nix-env doesn't work (otherwise it will overwrite the profile, removing all packages other than the newly-installed one).
(writeTextFile {
name = "break-nix-env-manifest";
destination = "/manifest.nix";
text = ''
throw ''\''
Your user environment is a buildEnv which is incompatible with
nix-env's built-in env builder. Edit your home expression and run
update-profile instead!
''\''
'';
})
# To allow easily seeing which nixpkgs version the profile was built from, place the version string in ~/.nix-profile/nixpkgs-version
(writeTextFile {
name = "nixpkgs-version";
destination = "/nixpkgs-version";
text = lib.version;
})
];
}
@jnetod
Copy link

jnetod commented Nov 29, 2020

@AndersonTorres I tried, without success, to reproduce this with nix profile; but I found a way to use buildenv with flakes (in a certain way), you'll need 3 files, besides buildenv.nix:

# flake.nix

{
  description = "buildenv + flake";

  inputs = {
    nixpkgs.url = github:NixOS/nixpkgs/nixos-20.09;
    flake-compat = {
      url = "github:edolstra/flake-compat";
      flake = false;
    };
  };

  outputs = { self, nixpkgs, flake-compat }: rec {
    system = "x86_64-linux";
    packages.${system}.user-env = import (./buildenv.nix) { pkgs = nixpkgs.legacyPackages.${system}; };
    defaultPackage.x86_64-linux = self.packages.${system}.user-env;
  };
}
# default.nix

(import (
  let
    lock = builtins.fromJSON (builtins.readFile ./flake.lock);
  in fetchTarball {
    url = "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz";
    sha256 = lock.nodes.flake-compat.locked.narHash; }
) {
  src =  ./.;
}).defaultNix
# flake.lock

$ nix flake update

Then you can proceed with the installation, just like buildenv.nix instructions.

To update your buildenv you'll need to update your flake.lock first. You could try to put some update logic on update-profile script.

References:

@PanAeon
Copy link

PanAeon commented Aug 3, 2021

@AndersonTorres yet another way to use buildEnv with flakes and nix profile:

 {
  description = "A home flake";
  # original idea https://gist.github.com/suhr/4bb1f8434d0622588b23f9fe13e79973

  inputs = {
    nixpkgs-unstable.url = "nixpkgs/nixpkgs-unstable";
    neuron.url = "github:srid/neuron";
    neuron.inputs.nixpkgs.follows = "nixpkgs-unstable";
  };

  outputs = { self, nixpkgs, nixpkgs-unstable, neuron }: 
    let
      overlay-unstable = final: prev: {
        unstable = import nixpkgs-unstable {
          system = "x86_64-linux";
          config.allowUnfree = true;
        };
      };
    in {
    defaultPackage.x86_64-linux =  with import nixpkgs { system = "x86_64-linux"; overlays = [ overlay-unstable ]; config = { allowUnfree = true; }; }; buildEnv {
      name = "home-env";
      paths = [
      neuron.defaultPackage.x86_64-linux
     
      unstable.neovim
    
      elvish
      mednafen
      firefox-wayland
      
      ...
      ];
    };
  };
}

To install nix profile install .
And to upgrade nix profile upgrade '.*'

@AndersonTorres
Copy link

AndersonTorres commented Aug 5, 2021

@PanAeon many thanks! I have made a quick test and it is working! I will update my files accordingly.

P.S.: for those who may have problem with changing paths like the one below:

$ nix profile install
error: packages '/nix/store/d0wb2gam2pyksz2dsjx6sa83761jrrgi-hello-2.12/bin/hello' and '/nix/store/61n15131n3b9mn7ajvddbyfnv2b3pgjd-hello-2.12/bin/hello' have the same priority 5
; use 'nix-env --set-flag priority NUMBER INSTALLED_PKGNAME' to change the priority of one of the conflicting packages (0 being the highest priority)

The solution taken from NixOS/nix#6143 is just remove the profile before installing it.

@saccarosium
Copy link

Hello,
This gist helped me a lot for build my setup and I will share my config for hopefully helping others.

# flake.nix

{
  description = "my-user-env";

  # inputs are your dependencies
  inputs = {
    nixpkgs.url = "nixpkgs/nixos-unstable"; # where we get the packages
    flake-utils.url = "github:numtide/flake-utils"; # a small utility that helps not hardcode the system architecture. Something like 'x86_64-linux'
  };

  # Here we pass the input to outputs
  outputs = { self, nixpkgs, flake-utils }:
    flake-utils.lib.eachDefaultSystem (system:
      let pkgs = nixpkgs.legacyPackages.${system};
      in
      rec {
        # Here we are importing a file in the same directory of the flake.nix, named 'profile.nix'
        packages.${system}.user-env = import (./profile.nix) { pkgs = nixpkgs.legacyPackages.${system}; };
        defaultPackage = packages.${system}.user-env;
      }
    );
}

ℹ️ NOTE
You have to change <path-to-flake> to the location you want

# profile.nix

{ pkgs ? import <nixpkgs> { }, name ? "user-env" }: with pkgs;

buildEnv {
  inherit name;
  # to get all the symlink we need
  extraOutputsToInstall = [ "out" "man" "lib" ];
  paths = [
    # ... put your packages here
    
    # this will create a script that will rebuild and upgrade your setup. Is using shell script syntax
    (writeScriptBin "nix-rebuild" ''
      #!${stdenv.shell}
      cd <path-to-flake> || exit 1
      nix flake update
      nix profile upgrade '.*'
    '')
    
    # puts in your root the nixpkgs version
    (writeTextFile {
      name = "nixpkgs-version";
      destination = "/nixpkgs-version";
      text = lib.version;
    })

  ];
}

To install your packages you only need to cding in the directory containing the flake and run nix profile install . and run nix-rebuild when you want to update

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment