How to specify the shell for a remoting agent

Hi all!

Simple question, hopefully with a simple answer, but one I haven’t found yet:

How do I specify the shell options for JNLP agents?

Note: this is about which shell, but about how the shell is invoked. IE I want a full login shell, so that the agent picks up environment and shell variables in the regular places, just as a normal user would. It seems that this is not happening with the JNLP connectors.

Is there a Java argument I can set for the jnlp? Is there something I can add to my (declarative) pipelines?


1 Like

I don’t think jnlp agents have shells. Don’t they connect to jenkins?

Inside your /mange page theres a setting for default shell, you can probably set it there.

@brucellino Yeah I think this is just how the sh step behaves. If it’s configurable, then I don’t know about it.

I have a library called the jenkins-std-lib that has a custom bash step that you might be interested in. Here are some of its best features:

  • It sources bash_profile
  • It can run commands with no output to the console
  • It returns a result object that has the exitCode, stdOut, stdErr and the combined output.
  • If the command throws and error, the exception has the same properties as the result object.

You can see a working example job here.

on the /configure page

the default I believe is /bin/bash? you can also add shebangs at the top of your sh() step, but i don’t think you’d want to do that every single case.

sh('''#!/bin/zsh -l
echo zshspecificthing

Thanks for your feedback, @shadycuz and @halkeye

Yes, this is indeed where one can set the shell, but not the options of that shell.

Indeed - not only do I not want to do this in all of my (declarative) pipelines - I also want to be able to change it in once place instead of N.

What I want – and what I imagine many other pipeline maintainers want – is a pipeline step that does what @shadycuz 's library does:

because it is in bash_profile where I imagine folks handle much of the environment for the job.

In my case, we are re-implementing the “tool location” functionality:

  • for NodeJS versions, we use
  • for Java versions, we use SDKman!
  • for python things we use Virtualenv

The setup for all of these things is kept in /etc/profile and in .bashrc, which of course only get loaded if the shell is interactive. I prefer to use these non-Jenkins-specific tools to create the build environment, because they are easily reproducible on developer environments.

However, reading the bash docs a bit more carefully, I see that there is a section on invoking non-interactive shells which states:

Invoked non-interactively

When Bash is started non-interactively, to run a shell script, for example, it looks for the variable BASH_ENV in the environment, expands its value if it appears there, and uses the expanded value as the name of a file to read and execute. Bash behaves as if the following command were executed:

if [ -n "$BASH_ENV" ]; then . "$BASH_ENV"; fi

but the value of the PATH variable is not used to search for the filename.

So I think the answer can be summarised as follows:

  • sh is a well-defined step, don’t try to be cute with it.
  • if you want to get cute, try setting ${BASH_ENV} in the pipeline - at least you only have to set it once per pipeline.
  • if you want a customisable shell, write your own library for it

Thanks very much!

can you not do #!/bin/bash -l? I haven’t tried it but it seems like the proper solution.

you could also just do

source /etc/profile
do thing1
do thing 2
do thing 3

or use the jenkins tools() system so you don’t need to install things on your system images
you could also use docker images and not have to worry about versions at all
you could add env variables (like PATH) when you add manage the agent, then it won’t matter what shell your using.

There’s lots of decent solutions to the problem your having, without having to hack the solution your wanting.

Edit i can’t find alink for managing env variables, but i added a screenshot to this post

Yes! indeed this would have been ideal - but my controller didn’t like that. Setting the shell in the /configure page to /bin/bash -l gave an error. If the sh step could be modified to support this kind of thing, it would be great.

Some more considerations - good suggestions in general, but not good for us in particular:

This is not a good option, at least for us, because it makes it difficult to run steps “out-of-band”, and creates a “special” environment in CI that cannot be reproduced in the developer’s machine.

Indeed, we do have containers for the build environment. They include a modified version of the standard Jenkins startup script – starting Vault Agent for example and provisioning relevant secrets during execution.

However, instead of maintaining NxMx… individual containers, we maintain one which is tested for the matrix of tool combinations. This means there is one agent label to select, very easy to maintain pipelines.

The key ingredient is the SDK selection tool, not the SDK itself. This greatly reduces the overhead in maintenance of images, not to mention the sum size of images we have to store in the registry, makes it easy to test combinations and allows developers to experiment with new versions.

We indeed do that, but this is not sufficient to setup the execution environment. The setup scripts of nvm and sdk need to be executed themselves in order to work.

The one variable that does show promise however is ${BASH_ENV} which would force the inclusion of the setup scripts that would otherwise have been used automatically.

I’m out of ideas sorry. I think you can modify your startup scripts (.bashrc/.bash_profile/etc) to ignore if your a login shell or not.

That being said, I switched from a bunch of version management tools to asdf a couple years ago and never looked back. Not only does it support tons of different languages and frameworks via plugins, and .tool-versions in the project itself. But I believe the majority of it works just with a couple env variables

Note that not all distributions have bash in /bin/. The #! should work, but maybe you need /usr/bin/bash, or even #!/usr/bin/env bash to run whatever bash in first on $PATH

Also, you could just create a default Groovy library with a myshell function that wraps the shell step, passing in the right shebang and whatever else you need, so you only need to do it once.

Our hack for this was to wrap calls to things that needed a version manager, e.g cnp-jenkins-library/YarnBuilder.groovy at 48109489e4a1075196142f9c1022c38be1f52ddf · hmcts/cnp-jenkins-library · GitHub

1 Like