What is the Flakes version of "reproducible interpreted scripts"?

i.e., https://nix.dev/tutorials/reproducible-scripts

but with flakes and not traditional Nix.

I have some Bash stuff that I want to preserve.

Also, if there was a way to do it the Nix way but only if Nix was installed, that would be sweet, too.

EDIT: I ended up doing it this way; adapt to your own needs, but this seems to work well for now while not requiring any other files:

https://gist.github.com/pmarreck/18a27a2af8688bef2f51992763ded238

Posted Apr 26, 2023, 11:26 PM UTC · edited

1540
mtndewforbreakfast•

I would write a package output built on something from the family of writeShellApplication, writeShellScriptBin, etc in roughly that order of preference:

https://ryantm.github.io/nixpkgs/builders/trivial-builders/#trivial-builder-writeShellApplication

14
mtndewforbreakfast•

If they already exist as files you can use builtins.readFile instead of inline strings.

5
ABC_AlwaysBeCodingOP•

oh this is nice. does it solve the problem of calling shell functions defined there at runtime though? where the PATH might have changed to point to different versions of any deps utilized...

1
mtndewforbreakfast•

There's some substitution helper that may help if I correctly understand your question, but I can't recall the name to provide a link.

5
AbathurSchmabathur•

I'm also not 100% on whether it answers the question, but I imagine you're thinking of https://github.com/abathur/resholve (doc in nixpkgs: https://github.com/NixOS/nixpkgs/blob/master/pkgs/development/misc/resholve/README.md)

Edit: doesn't address flakes, but I have a general ~breakdown of the main approaches to using Nix for shell dependencies in https://www.t-ravis.com/post/shell/no_look_no_leap_shell_with_nix/

5
ABC_AlwaysBeCodingOP•

just to clarify with an example: I run a shell file defined in this way; it defines some shell functions in my current environment. Later on, I call the function, but I'm now in a different scope/profile/environment/whatever, so when code inside the function calls (for example) curl, will it use the curl specified within the nix file or will it use the first curl it finds on whatever value PATH has at that time it is run?

EDIT: I guess it would have to use a fully-specified bin path in order to be deterministic

2
Aidenn0•

Might be a better way, but I do roughly this:

EDIT: Preserving this for history but see my comment below for improved version.

​

#!/bin/sh
if test "$0" != bash; then
    nixpkgs="github:NixOS/nixpkgs/bc024e10efdf5a41f94d4f82c053ce11abc137ae"
    deps="bash jq curl"
    deps="$(for d in $deps; do printf '%s#%s\n' $nixpkgs $d; done)"
    exec nix shell $deps -c bash -s "$0" "$@" <"$0"
fi
BASH_ARGV0="$1"
shift
#Script goes below this line

Quick breakdown:

  1. Shebang is /bin/sh which should be on any POSIX system, not just NixOS
  2. We check if "$0" is "bash"; as long as the name of this script isn't "bash" then it can't be this on initial invocation
  3. if "$0" is bash, we build up a list of flakerefs using a specific nixpkgs revision (in this example: bash, jq, and curl
  4. we exec nix shell, passing the script itself as stdin and the invocation name as the first parameter
  5. if we reach past the exec line, we have been invoked recursively; set "$0" using BASH_ARGV0, shift the parameters

[edit]

Just noticed there was a request for transparently working if nix is not present; this is easy, after this line:

if test "$0" != bash; then

Add:

    if ! command -v nix > /dev/null; then
        exec bash -s "$0" "$@" <"$0"
    fi

This will check if the nix executable is reachable, and if not then it will just invoke bash without any nix shell.

3
ABC_AlwaysBeCodingOP•

echo $0 from within a file, when sourced from the outside in the terminal, returns "-bash" and not "bash". Is this expected? Not sure what the significance of the leading hyphen is

I guess my general question is, if the script might additionally possibly be sourced, how could that get handled as well? (I'm probably asking for a lot, because your solution here is already pretty clever!)

1
ABC_AlwaysBeCodingOP•

I suppose this only works in bash and not sh lol

test "${0#-}" != bash

1
Aidenn0•

Note the shebang is /bin/sh; the header explicitly calls bash so we can rely on bash. See my other comment though because I was unhappy with checking "$0" anyways.

1
Aidenn0•

The leading hyphen means it's a login-shell; it has nothing to do with a terminal; if you C-A-F2 to the console and login, you will see $0 being "-bash"

The way I invoked bash in my script, the recursive execution of bash should never cause $0 to be "-bash"

On reflection there are probably downsides to using "-s" (namely you lose the stdin, so you can't pipe stuff thorugh it).

changing it to something like

some_magic_var=some_magic_value exec bash "$0" "$@"

And then checking for some_magic_var to be set (and unsetting it) is probably better

[edit]

Here's Version 2. I like this generally better. Conventionally environment variables are ALL_CAPS so nix_wrapper_magic is an unlikely collision; the script keeps this convention by unsetting it right after execution.

#!/bin/sh
if test "$nix_wrapper_magic" != magic; then
    if ! command -v nix > /dev/null; then
        nix_wrapper_magic=magic exec bash "$0" "$@"
    fi
    nixpkgs="github:NixOS/nixpkgs/bc024e10efdf5a41f94d4f82c053ce11abc137ae"
    deps="bash jq curl"
    deps="$(for d in $deps; do printf '%s#%s\n' $nixpkgs $d; done)"
    nix_wrapper_magic=magic exec nix shell $deps -c bash "$0" "$@"
fi
unset nix_wrapper_magic 
#Script goes below this line
1
ABC_AlwaysBeCodingOP•

the formatting on that got borked…

1
Aidenn0•

You must be using old Reddit; I used triple quotes. I changed to the old formatting so should work now.

2
ABC_AlwaysBeCodingOP•

also as far as nix packages go, what's the diff between bash and bashInteractive? Also, same question but awk and awkInteractive

1
Aidenn0•

For bash at least I think it's just readline support

1
matthew-croughan•

This PR, but it's not merged yet https://github.com/NixOS/nix/pull/5189

3
ABC_AlwaysBeCodingOP•

Fascinating! Subscribed!

1
clhodapp•

If I may promote my own thing: https://github.com/clhodapp/nix-runner

It's not quite what you asked for because it's not transparent but... You can run a script like this without nix by explicitly executing it with your shell.

2
ABC_AlwaysBeCodingOP•

This looks pretty awesome!

My only recommendation maybe would be to be able to specify a with scope so that you wouldn't have to nixpkgs# every package on separate lines, or maybe something that would look like #!packages_with nixpkgs#[bash coreutils jq nix] or some such, but that's probably a difficult transformation to do with sed alone =)

1
clhodapp•

I'm probably going to stick with the official Nix syntax for installables rather than adding my own extensions. However, you can bind to a shorter name like e.g. n instead of nixpkgs. At that point, it is n#bash n#coreutils n#jq, and n#nix

2
Secret-Ad-7042•

I'm not sure if I understand what you exactly mean. But here are my scripts that can installed using flakes. https://github.com/Tanish2002/bin

2
[deleted]•

[deleted]

1