tl;dr: you’re missing the
--login
flag in yourbash
execution.
Hey,
yesterday I was trying to test some ansible roles I had written and to do so I was using a containerized version of Ubuntu that I tailored for doing the job - cirocosta/ubuntu.
The image does its job pretty well: it can run with systemd
as the PID 1, it has SSH and I can mess around with systemd services, units .. etc etc as if I had a working “VM” (given the set of boundaries of how far we can simulate a VM). Anyway … I faced an issue: my ansible roles were setting some scripts at /etc/profile.d/
but they were not being executed! How dare they?
It turns out that something pretty simple was going on - the default shell (bash in that case) wasn’t being executed as “login shell”.
Looking at bash(1)’s man page:
--noprofile
Do not read either the system-wide startup file
/etc/profile or any of the personal initialization
files ~/.bash_profile, ~/.bash_login, or ~/.profile.
By default, bash reads these files when it is invoked
as a login shell (see INVOCATION below).
Invocation
A login shell is one whose first character of argument
zero is a -, or one started with the --login option.
i.e, when we run bash <blabla>
by default, it won’t run as a login shell and it turns out that profile
files are only read when running it as login
. Does it? We can easily check that with strace
:
# create a script to be sourced by bash on startup
echo "export FOO=bar" > /etc/profile.d/my-script.sh
# create a simplistic exec file
echo "echo hey!" > ./exec.sh
# execute it without '--login'
strace -e open bash exec.sh
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
...
open("/usr/lib/x86_64-linux-gnu/gconv/gconv-modules.cache", O_RDONLY) = 3
open("exec.sh", O_RDONLY) = 3
hey!
+++ exited with 0 +++
# execute it with '--login'
strace -e open bash --login exec.sh
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
..
open("/dev/tty", O_RDWR|O_NONBLOCK) = 3
open("/usr/lib/locale/locale-archive", O_RDONLY|O_CLOEXEC) = 3
open("/usr/lib/x86_64-linux-gnu/gconv/gconv-modules.cache", O_RDONLY) = 3
open("/etc/profile", O_RDONLY) = 3
open("/etc/profile.d/", O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC) = 3
open("/etc/profile.d/my-script.sh", O_RDONLY) = 3
open("exec.sh", O_RDONLY) = 3
hey!
+++ exited with 0 +++
Closing thoughts
Do you need a full ubuntu container? Should you care about /etc/profile.d
in a container? Probably not. Honestly, aside from testing ansible
roles, I never needed.
It’s pretty cool how we can super easily verify a behavior making use of a combination of man
to check the docs and strace
to check the syscalls involved. Very handy.
If you have any questions or just noticed that I wrote something that’s totally wrong or that could be made better, reach out! I’m @cirowrc on Twitter. You can also subscribe to the newsletter to keep up with some stuff that I find very useful.
Cheers!