Coreutils 9.0 won't build

home.packages makes stuff available in your regular environment. Shells and most application drawers will find the things installed there.

Most services, windowmanagers, or otherwise auxiliary programs get a limited environment which does not get the ~/.nix-profile/bin into their PATH, as that would make it easy for them to have undocumented or unrecognized runtime dependencies to slip in. Basically: avoid the mess that all the other distros have to maintain.

You have to configure/wrap/write configuration for such programs in a way, that they either get wrapped with some extended PATH in the environment or such that corresponding configuration does have the absolute storepath to the binary in question in it.

Lets take a look at my config (which I see right now I really should fix for amixer!)

Here I map the media buttons on my keyboard to run certain commands. This module is far from being perfect, but it serves an example for the purpose.

The generated configuration looks like this:

   ,awful.key({ }, "XF86AudioLowerVolume", function () awful.util.spawn("amixer set Master 4%-") end),
  awful.key({ }, "XF86AudioMute", function () awful.util.spawn("amixer set Master 1+ toggle") end),
  awful.key({ }, "XF86AudioNext", function () awful.util.spawn("/nix/store/1xh8bjjvw1vik42fyvxh5xsrqxr8n5wj-playerctl-2.4.1/bin/playerctl next") end),
  awful.key({ }, "XF86AudioPlay", function () awful.util.spawn("/nix/store/1xh8bjjvw1vik42fyvxh5xsrqxr8n5wj-playerctl-2.4.1/bin/playerctl play-pause") end),
  awful.key({ }, "XF86AudioPrev", function () awful.util.spawn("/nix/store/1xh8bjjvw1vik42fyvxh5xsrqxr8n5wj-playerctl-2.4.1/bin/playerctl previous") end),
  awful.key({ }, "XF86AudioRaiseVolume", function () awful.util.spawn("amixer set Master 4%+") end)

Here you see the playerctls being properly expanded into absolute program paths. The configuration will probably change with every other update, but I do not care, as nix and HM regenerate it for me whenever necessary.

This is how we deal with runtime dependencies in nix. We make them explicit whenever possible.

Yes, got it. I have done this for packages similar to your example of pkgs.playerctl, but what is the properly expanded program path for the scripts I make in my config using writeShellApplication?

It doesn’t seem to be this:

${pkgs.rofi-bluetooth}/bin/rofi-bluetooth

You gave them a name in the let.

So, if I do this:

home.packages = let
  rofi-bt = writeShellApplication {
    name = "rofi-bluetooth";
    runtimeInputs = [ rofi bluez coreutils gnugrep ];
    text = builtins.readFile ./rofi-bluethooth.sh;
  };
  in [
    rofi-bt
    pkgs.feh
    pkgs.ffmpeg
  ];

And then try to call this script like this:

${pkgs.rofi-bt}/bin/rofi-bluetooth

I get this:

error: attribute 'rofi-bt' missing

       at /home/user/.config/nixpkgs/home.nix:977:15:

          976|         type = "custom/script";
          977|                               exec = "${pkgs.rofi-bt}/bin/rofi-bluetooth --status";
             |               ^
          978|                             # exec = "rofi-bluetooth --status";
       Did you mean rofi-rbw?
(use '--show-trace' to show detailed location information)

If I call it like this instead:

${rofi-bt}/bin/rofi-bluetooth

I get this:

error: undefined variable 'rofi-bt'

       at /home/user/.config/nixpkgs/home.nix:977:15:

          976|         type = "custom/script";
          977|                              exec = "${rofi-bt}/bin/rofi-bluetooth --status";
             |               ^
          978|                          # exec = "rofi-bluetooth --status";
(use '--show-trace' to show detailed location information)

A binding defined in a let/in is only available in the expression following the in. You can’t use the name in another expression. You need to either move the binding to a bigger scope or define a second binding for the second use.

And how would I do this? I don’t even know what this means.

Move the declaration of the binding to surround the returned attribute set.

so change this broken code:

{
  a = let b = 1; in b;
  c = b;
}

to this working:

let b = 1; in {
  a = b;
  c = b;
}

OMG. I think I am finally making progress! And more importantly, I am slowly understanding things a bit better.

Moving the let statement to include more statements seems to have done the trick. Also, your suggestion to use writeShellApplication also really helped me out.

Now, the last remaining item is how to call my shell script from within the polybar config. The polybar statement inside home.nix look like this:

exec = "rofi-bluetooth --status";

But if I use this, polybar spits out the error

/bin/sh: rofi-bluetooth: command not found

However, if I use this instead:

exec = "~/.nix-profile/bin/rofi-bluetooth --status";

it works fine.

Is there a better way to specify the path?

This is the quick workaroung and only works if “installed” via home.packages. A more reliable way to solve your problem:

exec = "${rof-bluetooth}/bin/rof-bluetooth --status";

Yes, that did it. Thanks so much for your continued help. I know I didn’t take the most direct route to the solution, but I finally got there.