3

I'm using launchd to set an env var at startup using launchctl setenv.

The script works fine when the variable is hardcoded in it, but unfortunately I need to get this variable dynamically at startup from a script.

The reason I'm trying to use launchd for this (rather than using .zshrc to run export MYVAR=...) is that the script will prompt me for a password before returning the desired value, and I don't want to enter the password every single time I open a shell.

If I were using Linux, I could simply use .zprofile for this, which is only sourced for login shells, which only happens at system login. But this is MacOS: every shell is a login shell, and so every shell sources .zprofile together with the rest of files. Hence my attempt at doing this in launchd.

So my question is:

Is there any way for launchd to "read" the output from an interactive shell script, and then use that output as input for the launchctl setenv command?

5
  • 1
    You could just add logic to your shell startup that would look to see if the variable was already set before trying to set it. Commented Jun 29, 2023 at 18:30
  • There is /pathtoscript/myscript | xargs launchctl setenv. For example, read -p "password:" pw; echo "mypassword $pw" | xargs launchctl setenv would set mypassword to what is read by the read command. To verify this works in Terminal, you would have to quit and reopen Terminal, then enter echo "$mypassword". Commented Jun 29, 2023 at 19:15
  • Thanks @MarcWilson this is what I ended up doing. I'm explaining in a bit more detail in my own answer.
    – VMX
    Commented Jul 1, 2023 at 9:44
  • 1
    “Every shell is a login shell?” That’s not correct. Why can’t you put this in .zhenv that will be read for all shells; interactive and non-interactive , login and non-login. This script you referenced, is it asking you fir a password because it requires root privileges or it has it’s own authentication?
    – Allan
    Commented Jul 1, 2023 at 13:22
  • Yes. As far as I know, and unlike pretty much all other Unix systems, in MacOS every shell is a login shell. This means every shell loads not only .zshrc and .zshenv, but also .zprofile. As a result, there's nowhere you can put something that will only be sourced once. Even if you put it only in .zprofile, it will be executed in every single terminal window you open. The script I need to run simply pulls a password out of my BitWarden vault, which requires my master password.
    – VMX
    Commented Jul 1, 2023 at 17:16

2 Answers 2

1

You could add a LaunchAgent that runs your script at login and then calls launchctl setenv with the result. Or better perhaps, wrap the script in an Automator app and add it to your login items.

1

I ended up solving this the way @MarcWilson suggested. However, it was a bit trickier than it looks.

The challenge here was that, if I simply used export MYVAR=... inside .zshrc, I think MYVAR was not being picked up by .zshrc itself in its next execution. As such, I would still get prompted for my password every time I opened a shell, even though MYVAR was already available to all interactive shells!

Please correct me if I'm wrong (it was all a bit difficult to test), but if I got it right, my understanding is that by using export, you cannot set an env variable for the same script that's setting it (.zshrc in this case). It will be inherited by its child shells, but not by itself.

Luckily, the fix was easy. Besides EXPORT=..., I also had to do launchctl setenv MYVAR ... in the same script:

if [[ -z "${MYVAR}" ]]
then
    # Get my session token, which prompts me for my password
    MYTOKEN=$(command_to_get_token)

    # Use my session token to obtain the env value I need to set
    MYVAR=$(command_to_get_myvar --token $MYTOKEN)

    # Set the env var using BOTH launchctl and EXPORT 
    launchctl setenv MYVAR $MYVAR
    export MYVAR=$MYVAR
fi

Now, .zshrc only prompts me for my password on first run, because it does pick up MYVAR in all subsequent runs (until I reboot my PC or I manually unset it of course).

As said, this was a bit tedious to test as launchctl env vars tend to stick even after you unload it, which means I had to reboot my Mac several times to confirm both commands were really needed. But if you think this isn't the case and I'm doing something redundant, please let me know!

I'm marking this as resolved for now since it's working as intended.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .