docker-php icon indicating copy to clipboard operation
docker-php copied to clipboard

/etc/entrypoint.d if script ends with `exit 0` no other scripts are executed

Open rburgst opened this issue 1 year ago β€’ 2 comments

Steps To Reproduce

  1. I am running 8.3-fpm-apache with a bunch of custom entrypoint scripts
  2. as of 3.4.0 my later scripts are not executed because my 11-xxx.sh ends with a exit 0, this causes the while loop to terminate early
# Execute scripts from /etc/entrypoint.d/ in numeric order
find /etc/entrypoint.d/ -type f -name '*.sh' | sort -n -t- -k1 | while IFS= read -r f; do
  if [ -e "$f" ]; then
      if [ "$LOG_OUTPUT_LEVEL" = "debug" ]; then
          echo "Executing $f"
      fi
      if ! . "$f"; then
          echo "Error executing $f" >&2
          exit 1
      fi
  else
      echo "Warning: $f not found" >&2
  fi
done

Outcome

What did you expect?

all entrypoint scripts should execute successfully

What happened instead?

only scripts until my 11-xxx.sh scripts are executed, all subsequent scripts are skipped

Affected Docker Images

serversideup/php:8.3-fpm-apache (at least)

Anything else?

This is copilots take on the problem image

To be honest I have no idea why it did work with 3.3.0 and lower, the change from https://github.com/serversideup/docker-php/issues/410 does not really appear to make any difference in that regard (to me), but it seems to do it. It would be more robust if you can use your good old exit <code> in your scripts without breaking the startup sequence.

rburgst avatar Oct 23 '24 15:10 rburgst

So this is the culprit:

https://github.com/serversideup/docker-php/blob/825625bfee3fa2849328ba3386a2e8cb3b63b8da/src/common/usr/local/bin/docker-php-serversideup-entrypoint#L42-L54

Line 47 is where it gets executed.

I forget the exact reason why we did it this way, but if there is a better way to execute sub-scripts -- I am all ears.

To get your script to exit properly, you need to use return 0. The exit 0 will stop since it's using the same shell.

jaydrogers avatar Oct 23 '24 15:10 jaydrogers

found this https://unix.stackexchange.com/questions/730580/can-a-script-be-run-as-a-subshell which seems to match with an earlier implementation of this script.

rburgst avatar Oct 24 '24 05:10 rburgst

I wonder if @szepeviktor would have any opinions on this as he is a shell wizard πŸ§™

jaydrogers avatar Oct 24 '24 14:10 jaydrogers

. is the short version of source.

source does not start a subshell, $f is executed in the same shell environment. - Here exit is dangerous.

sh $f or ./f does open a subshell. - A subshell is exit-tolerant, exit will exit from the subshell but not from the current script.

szepeviktor avatar Oct 24 '24 14:10 szepeviktor

Thanks! Makes sense.

I guess my question is Which method should we follow? Existing shell or subshell?

In the case of allowing people to run their own scripts, should we put them in their own subshell or keep it all within our existing shell?

We would need the environment variables to be accessible from Docker Compose files, etc, so I think that might be a reason why I went with the same shell? It's been a while since I looked at this and I forgot why I went this way -- but it was my first time building something like this so that's why I opened this up for discussion

jaydrogers avatar Oct 24 '24 14:10 jaydrogers

There is no solution πŸ’£ only work-arounds.

Shell scripting is for simple tasks, there is no security in shell.

The only thought that does not make my stomach turn is: use export for every variable you want to make available.

szepeviktor avatar Oct 25 '24 08:10 szepeviktor

Thanks @szepeviktor!

@rburgst: Are you okay with just using return 0 instead of exit 0?

jaydrogers avatar Nov 07 '24 18:11 jaydrogers

I have changed our scripts accordingly however it’s a bit unintuitive or might be actually a footgun.

rburgst avatar Nov 07 '24 18:11 rburgst

I am all ears on proposals.

I have @cappuc who submitted a PR to have it be return 0 and it seems like you're suggesting it to be exit 0.

We can't support both, so let's work together as a community and figure out what is the best DX and most secure πŸ˜ƒ

jaydrogers avatar Nov 07 '24 18:11 jaydrogers

So I've been looking into this deeper and trying to use AI to help guide a direction with this, it had a helpful explanation as I asked it about this section:

https://github.com/serversideup/docker-php/blob/f2250d8f6c4932db22aa161b7576c5eaa6afd54e/src/common/usr/local/bin/docker-php-serversideup-entrypoint#L41-L54

image

It pointed me to an example of the official Postgres entrypoint, which follows a similar method to what we're doing.

I also found this article which was really helpful: https://www.docker.com/blog/docker-best-practices-choosing-between-run-cmd-and-entrypoint/

Conclusion

  • It seems like return 0 is the recommended way to go
  • I will get this updated in the documentation on why

If you have any more thoughts, I would love to hear them.

jaydrogers avatar Nov 07 '24 19:11 jaydrogers

I made that PR to change exit 0 to return 0 because I encountered the same problem.

With the current implementation exit 0 can be used to prevent subsequent entrypoint scripts to run without exiting with an error (I don't if this can be usefull in some cases).

I don't know what is the best solution.

cappuc avatar Nov 07 '24 20:11 cappuc

Thanks! @cappuc! I think the return 0 without a subshell is the way to go (at least my gut is telling me that).

I documented it here: https://github.com/serversideup/docker-php/commit/c98009b07516c3f51b77106cacc66d93be86b087

I am all ears if someone has a better solution in the meantime

jaydrogers avatar Nov 07 '24 20:11 jaydrogers

I added this in the docs: https://serversideup.net/open-source/docker-php/docs/customizing-the-image/adding-your-own-start-up-scripts#dont-use-exit-0-in-your-script

I am closing for now, but if there is any further discussion on this item, please comment below πŸ‘

jaydrogers avatar Nov 07 '24 20:11 jaydrogers

@jaydrogers This is a surprise gift 🎁 for the permission-less shell script.

a.sh

exit()
{
    return
}

export -f exit
source b.sh
echo "End of a.sh"

b.sh

echo "This is b.sh"
exit 3

Start a.sh: b.sh can not exit!

szepeviktor avatar Nov 07 '24 21:11 szepeviktor

Fun! So should I document that you shouldn't source other scripts from your script?

I'm not aware of a use case where someone would want to do this.

Entrypoint scripts are just simple start up scripts to execute something.

It seems like the issue you bring up would affect the official Postgres images too?

jaydrogers avatar Nov 07 '24 21:11 jaydrogers

where someone would want to do this.

You should define that function in your script where you call a user script. (it is like immunizing against exit)

szepeviktor avatar Nov 07 '24 21:11 szepeviktor

If you use exit 0 in your script, it will stop the execution of the rest of the entrypoint scripts.

πŸ‘‰πŸ»

"it won't stop the execution"

szepeviktor avatar Nov 07 '24 21:11 szepeviktor

Ohhhhhhh, now I get it πŸ˜ƒ

I like where this is going, but will it work in /bin/sh? We need to make sure it's /bin/sh for Alpine.

To my understanding this might only work in /bin/bash.

jaydrogers avatar Nov 07 '24 21:11 jaydrogers

Yes I'm a Bashist with a hard hat.

Please try it in your shell!

szepeviktor avatar Nov 07 '24 21:11 szepeviktor

Trust me, I wish I could do this in /bin/bash πŸ˜ƒ

export -f is not available in /bin/sh. I tried this:

###############################################
# Usage: docker-php-serversideup-entrypoint
###############################################
# This script is used to execute scripts from "/etc/entrypoint.d" and then
# execute the CMD passed in from the Dockerfile.

# Define exit function to prevent premature termination
exit() {
    return
}

# Export the exit function for use in scripts
export -f exit

# Execute scripts from /etc/entrypoint.d/ in numeric order
find /etc/entrypoint.d/ -type f -name '*.sh' | sort -V | while IFS= read -r f; do
    if [ -e "$f" ]; then
        if [ "$LOG_OUTPUT_LEVEL" = "debug" ]; then
            echo "Executing $f"
        fi
        if ! . "$f"; then
            echo "Error executing $f" >&2
            exit 1  # This still works for error cases since we're using the real exit
        fi
    else
        echo "Warning: $f not found" >&2
    fi
done

# Restore the original exit function
unset -f exit

My error

syntax error: bad function name

jaydrogers avatar Nov 07 '24 22:11 jaydrogers