“their path always changes” can be better thought of as “there are multiple paths”.
And those paths–those store paths–are obtained via usage of the Nix language. For example, lib.getExe pkgs.gnugrep.
I don’t remember ever explicitly addressing /run/current-system/sw/bin/. When an executable is in your path it hopefully reached there via idiomatic usage of Nix code such as the above or the NixOS environment.systemPackages or the Home Manager home.packages options or similar.
For use in a script, you have for example the runtimeInputs of pkgs.writeShellApplication or an inline lib.getExe or lib.getExe'.
Such usages attempt to guarantee the existence of the expected executable at a certain Nixpkgs revision and configuration in the store. Referring to /run/current-system/sw/bin provides no such guarantees.
Right: the pattern is to link store paths to well known locations or add store paths to well known environment variables. A big truth though of why you don’t want to reference is /run/current-system/sw/bin/ imo is that you aren’t actually creating a stable reference under gc. To wit: lets say I write a script that depends on git and to it in the following way pkgs.writeShellScriptBin "rev-parse" "/run/current-system/sw/bin/git rev-parse HEAD". There is no guarantee that git isn’t going to be removed from environment.systemPackages. Alternatively pkgs.writeShellScriptBin "rev-parse" "${lib.getExe pkgs.git} rev-parse HEAD" is a script that doesn’t care about the globally installed packages, it guarantees its own dependencies are available. Same for desktop files: always interpolate the derivation into the file, now your desktop files install the software they depend on to be valid if they themselves are referenced. This is imo the true zen of nix. Say no to plop!
If you write them, just use #! /usr/bin/env bash. It’s more portable, and thus good practice for linux in general. If you want to use scripts that aren’t written that portably without modification or packaging, there’s envfs (which is already built into nixpkgs, so you don’t need an external repo). It creates a FUSE filesystem you mount on /bin and /usr/bin which just looks up the command name in the calling processes PATH.
I agree many third parties write bad non-POSIX shebangs and sometimes auto-updates will overwrite the fixes you apply, or you won’t be able to modify the scripts at all.
@tejing’s solution with envfs (services.envfs) is great, but keep in mind that if you’re using Impermanence you won’t be able to boot, so instead the following hack in your config will do the trick:
In this case, you have to tell us what you do, and what would be “right”. Because, if there is no “right”, then there is no “wrong”, there is only “this is the most reliably working one across several devices”.
Of course, If I know in advance, I will only deploy my scripts to windows, I will respect that in the absent of a shebang and using .bat ending, but hey, that is wrong as well, as it wont work on Linux…
thanks, /usr/bin/env bash worked, I didnt test that.
And for my use cases I assume that every linux system has bash preinstalled. that is simply the case for regular desktop operating systems. Bash-compatible shells would hopefully add a symlink or alias to bash
It ensures that nothing accesses a partially initialized symlink before it’s fully written. And the move operation is (in 99% of filesystems) atomic so it doesn’t cost anything.
Sadly I can’t remember when and where I got the idea from, but it’s been a life-savior many times.
GNU coreutils’ lnis atomic, which it achieves by doing exactly what you do, so this is unnecessary in the context of a NixOS activation script. You’re just duplicating ln’s internal logic in bash.
I’m unsure about busybox, but POSIX in general isn’t atomic, so sure, in other contexts this can make sense. Just not for Linux desktops, or frankly in any context where your shebang is explicitly calling for bash.
You probably also shouldn’t use an activation script, as there is work in progress to deprecate them. Ordering a systemd unit so it triggers at the right time (maybe even in stage 1) is almost definitely doable. Use preservation instead of impermanence to avoid activation scripts completely and get sane unit ordering while you’re at it.
This also lets you use systemd-tmpfile to create the symlink instead, eliminating an early interpreter use and consolidating it all into the general-purpose file creation process.
Now that’s something I’ve missed. I also looked at the write-up to remove activation scripts and to remove interpreters altogether. Thank you as always, will do some more reading!
Simple and straight to the point. Nice, thank you!
#!/usr/bin/env bash has been standard practice for near decades now. Just use it.
There are a few exceptions to when you might want it, but if you’re special enough to need them you’re special enough to have read the manual to figure out if that’s you.
Similarly, if you must add more Bash to the world, consider set -euo pipefail at the top after the shebang. It’s slightly less universal, but again, is a good starting point.
Regardless, always use shellcheck and perhaps something like shfmt.
Are we the distro trying to fix dependency resolution or not? This is like advocating for dynamic linking to paths in /usr.
Of course you can do it, and we have been doing that for 40 years now, but NixOS is explictly the distro that avoids these standards because we think they’re harmful.
Use a shebang like that if you have a good reason to, and integration with non-NixOS environments can be a good reason, but if you use it in a NixOS context you’re kinda defying the purpose of the distro. It will work, but not for the right reasons.
Obviously it’s a judgement call and as an engineer you need to consider the trade-offs in your situation, but unless you’re just here for the hype using NixOS probably means that your situation values things that make this shebang a bad idea.