Add a path to the global $PATH with Ansible

When building certain roles or playbooks, I often need to add a new directory to the global system-wide $PATH so automation tools, users, or scripts will be able to find other scripts or binaries they need to run. Just today I did this yet again for my geerlingguy.ruby Ansible role, and I thought I should document how I do it here—mostly so I have an easy searchable reference for it the next time I have to do this and want a copy-paste example!

    - name: Add another bin dir to system-wide $PATH.
      copy:
        dest: /etc/profile.d/custom-path.sh
        content: 'PATH=$PATH:{{ my_custom_path_var }}'

In this case, my_custom_path_var would refer to the path you want added to the system-wide $PATH. Now, on next login, you should see your custom path when you echo $PATH!

Comments

Appreciate you documenting this, might want to include a mode: '0644' in the copy portion to enable other users to use the path.
Thanks again,
Greg

I think you’re missing the “export” command in the script content.

This method is okay for the first run, but it's not idempotent. Every time you run it, it will add it again. You can end up with a lot of duplicates. This lineinfile method will check to make sure the path does not already exist (in what ever file your modifying, the PATH in '/etc/environment' is used for non-interactive logins). If it's missing, it will append it to the PATH. One caveat, if it does exist and it's the first directory, it will move it to the end. It took me about 25 hours to fine tune this regex.

In this example, create a var named 'new_path' with the directory you want to add to PATH.

  lineinfile:
    dest:           "/etc/environment"
    state:          present
    regexp:         '^(PATH=\")({{ new_path }}:)?((?(2).*?(?!(?:.\"))|.*?(?!(?:{{ new_path }}))))(:{{ new_path }})?((?(4).*|\"))'
    line:           '\1\3:{{ new_path }}\5'
    backrefs:       yes

Much simpified idempotent solution:
- name: Add another bin dir to system-wide $PATH.
# Make sure its idempotent otherwise PATH grows on every run.
when: ansible_env.PATH is not search(my_custom_path_var)
copy:
dest: /etc/profile.d/custom-path.sh
content: 'PATH=$PATH:{{ my_custom_path_var }}'

I am trying my luck with this but, eventhough the /etc/profile.d/custom-path.sh is added as intendet neither the local PATH of my barman user, nor the global PATH has the added location /opt/barman/.local/bin.

So I am guessing one has to motivate the system to read/execute that script. How would you do that?

here is the adhoc code for that, however I tried to create an ansible custom module with this logic, but I keep getting error, I don't know what went wrong. I hope you could help on that.

before_file = open('/etc/environment', 'r')
before_lines = before_file.readlines()

env_path = "/opt/app/go/1.19.1/bin"

after_lines = []
# Strips the newline character
for line in before_lines:
if line.startswith('PATH="'):
paths = line.strip()[len("PATH=\""):len(line.strip()) - 1].split(":")
new_paths = []
found = False
for path in paths:
new_paths.append(path)
if path == env_path:
found = True
if not found:
new_paths.append(env_path)
after_lines.append('PATH="' + ":".join(new_paths) + '"' + "\n")
else:
after_lines.append(line.strip() + "\n")

# writing to file
after_file = open('/etc/environment-new', 'w')
after_file.writelines(after_lines)
after_file.close()

hi Jeff. Please disregard my previous comment about the PATH not being added.
logout/login is taking care of that. I just didn't go though the instructions till the end

- name: Set path in profile
ansible.builtin.lineinfile:
dest: /etc/profile
insertafter: EOF
line: "PATH=$PATH:{{ my_custom_path_var }}"