Best practice on handling executable paths in scripts when using home-manager on NixOS and non-NixOS?

Dear all,

are there any best practices on how to create scripts using home-manager, when running home-manager on NixOS and non-NixOS systems?

I can of course just add an if-condition to the script to check whether it is on NixOS or not and then set the proper executable paths.

Or is it safe to just rely on the shell to find the proper executable depending on the PATH?

In my case I am using home-manager on Fedora Kinoite, where there is a “native” kdialog in /usr/bin/kdialog, while I need to use the one from the Nix store on NixOS.

Thanks in advance!

Johannes

If you never prefer to fall back to your base OS’ binary for something that is installed in both, you may like to use writeShellApplication to always get Nix binaries while still being able to write more concisely when authoring the scripts.

1 Like

My main concern is that there are some things that get installed with home-manager/NixOS on one system but come from the base OS on another one.

I am not sure if it is a good idea to use e.g. kdialog from Nix on a Plasma desktop from Fedora. Not sure if there is some kind of version mismatch, so I would rather rely on the system one.

Hence my question how to best handle situations like these.

This seems like an “it depends” situation, where you’d have to make a choice whether you want to be self-contained and system independent (use writeShellApplication with all external exe’s the script refers to as package arguments) or use the system’s binaries.

I think (from your kdialog example), that if your script is specifically targeted at e.g. KDE, you could just rely on PATH and just use the executable’s names, since e.g. kdialog should be in it in your desktop session regardless Fedora or NixOS.

Probably the former method is more robust though, since normally I wouldn’t expect “version mismatches” on this level. What’s more, I’d even expect your nix’s kdialog to pickup your session theme etc., due to the way XDG... and DBUS etc. work. Of course this comes at the cost of increased dependencies for the script.

1 Like
  1. ensure all binary references are relative
  2. write your script in a file
  3. pkgs.resholve.writeScriptBin and builtins.readFile to read in the script mentioned in step 2. This way you are certain that all the required binaries are available on NixOS and the script will still work as normal on !NixOS.
2 Likes

That looks very interesting. Have you got an example using at at hand? Always easier to get started…

And: Wouldn’t that pick up e.g. kdialog on non-NixOS and install it in addition to the system one?

Maybe I misunderstood your question, but the way I see it, you have two options:

  1. if you have nix available where the script will run, use resholve to ensure all dependencies are in place. This means, if you call out to kdialog, it will be included at build time (not the one from the system environment).
  2. if you don’t have nix, the script will run without having been processed by resholve and will just pick whatever it finds in PATH. Getting the script onto your target system is of course also something that needs to be handled outside.

Example:

{
  maybeRebuildBalooIndex = pkgs.resholve.writeScriptBin "maybe-rebuild-baloo-index"
    {
      interpreter = pkgs.runtimeShell;
      inputs = with pkgs; [ coreutils plasma5Packages.baloo procps ];
      execer = with pkgs; [
        "cannot:${getBin plasma5Packages.baloo}/bin/balooctl"
        "cannot:${getBin procps}/bin/pkill"
      ];
    }
    builtins.readFile ./maybe-rebuild-baloo-index.sh
}

set -eEuo pipefail

STAT_FILE=$XDG_CACHE_HOME/baloo.state
FORCE_FILE=$XDG_CACHE_HOME/baloo.force_rebuild
test -f "$STAT_FILE" || touch "$STAT_FILE"

MSG="No action needed, leaving baloo index alone"
DO_RESET=0

if [ "$(stat --format='%Hd,%d' "$HOME/.config")" != "$(cat "$STAT_FILE")" ]; then
  MSG="Device id changed, resetting baloo index"
  DO_RESET=1
fi

if [ -e "$FORCE_FILE" ]; then
  MSG="Force reset requested, resetting baloo index"
  DO_RESET=1
  rm "$FORCE_FILE"
fi

echo "$MSG"
if [ "$DO_RESET" -eq 1 ]; then
  balooctl suspend
  balooctl disable
  pkill -9 baloo_file || true
  balooctl purge
  balooctl enable
  balooctl suspend
  stat --format='%Hd,%d' "$HOME/.config" > "$STAT_FILE"
fi

# in case it was started outside systemd
balooctl suspend
pkill -9 baloo_file || true

exit 0

Thanks Peter, that example is easy enough for me to get the grasp. The documentation for resholve is very large (and seems well written) but also kind of sophisticated. I was missing (or have overlooked) a simple example on how to use it.

And sorry if my question was too generic and the examples were a bit misleading. In most cases what you described would work, i.e. if I can just use the executable installed via nix because it does not have a strong relationship to system-related things.

Again, thanks for the answer and being patient with me… :slight_smile:

If you need to wrap more than one script, there is also resholve.mkDerivation. You can find an example of that in nixpkgs: https://github.com/NixOS/nixpkgs/blob/edfc8acaaf859b5101dad724b953cd84615379bb/pkgs/applications/networking/msmtp/default.nix

I am personally a huge fan of resholve as it brings us a lot of the guarantees from nix to shell scripts.

Again, thanks for the answer and being patient with me…

Any time. It’s a journey for all of us!

1 Like