Skip to content

Commit 48f2ad1

Browse files
committed
Functionalize the entrypoint to allow outside sourcing for extreme customizing of startup
1 parent a8613f4 commit 48f2ad1

File tree

1 file changed

+201
-117
lines changed

1 file changed

+201
-117
lines changed

docker-entrypoint.sh

Lines changed: 201 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -24,153 +24,237 @@ file_env() {
2424
unset "$fileVar"
2525
}
2626

27-
if [ "${1:0:1}" = '-' ]; then
28-
set -- postgres "$@"
29-
fi
27+
# check to see if this file is being run or sourced from another script
28+
_is_sourced() {
29+
# https://unix.stackexchange.com/a/215279
30+
[ "${FUNCNAME[${#FUNCNAME[@]} - 1]}" == 'source' ]
31+
}
32+
33+
# used to create initial posgres directories and if run as root, ensure ownership to the "postgres" user
34+
create_postgres_dirs() {
35+
local user="$(id -u)"
3036

31-
# allow the container to be started with `--user`
32-
if [ "$1" = 'postgres' ] && [ "$(id -u)" = '0' ]; then
3337
mkdir -p "$PGDATA"
34-
chown -R postgres "$PGDATA"
3538
chmod 700 "$PGDATA"
3639

37-
mkdir -p /var/run/postgresql
38-
chown -R postgres /var/run/postgresql
39-
chmod 775 /var/run/postgresql
40+
# ignore failure since it will be fine when using the image provided directory; see also https://github.com/docker-library/postgres/pull/289
41+
mkdir -p /var/run/postgresql || :
42+
chmod 775 /var/run/postgresql || :
4043

41-
# Create the transaction log directory before initdb is run (below) so the directory is owned by the correct user
44+
# Create the transaction log directory before initdb is run so the directory is owned by the correct user
4245
if [ "$POSTGRES_INITDB_WALDIR" ]; then
4346
mkdir -p "$POSTGRES_INITDB_WALDIR"
44-
chown -R postgres "$POSTGRES_INITDB_WALDIR"
47+
[ "$user" = '0' ] && find "$POSTGRES_INITDB_WALDIR" \! -user postgres - exec chown postgres '{}' +
4548
chmod 700 "$POSTGRES_INITDB_WALDIR"
4649
fi
4750

48-
exec gosu postgres "$BASH_SOURCE" "$@"
49-
fi
51+
# allow the container to be started with `--user`
52+
if [ "$user" = '0' ]; then
53+
find "$PGDATA" \! -user postgres -exec chown postgres '{}' +
54+
find /var/run/postgresql \! -user postgres -exec chown postgres '{}' +
55+
fi
56+
}
5057

51-
if [ "$1" = 'postgres' ]; then
52-
mkdir -p "$PGDATA"
53-
chown -R "$(id -u)" "$PGDATA" 2>/dev/null || :
54-
chmod 700 "$PGDATA" 2>/dev/null || :
55-
56-
# look specifically for PG_VERSION, as it is expected in the DB dir
57-
if [ ! -s "$PGDATA/PG_VERSION" ]; then
58-
# "initdb" is particular about the current user existing in "/etc/passwd", so we use "nss_wrapper" to fake that if necessary
59-
# see https://github.com/docker-library/postgres/pull/253, https://github.com/docker-library/postgres/issues/359, https://cwrap.org/nss_wrapper.html
60-
if ! getent passwd "$(id -u)" &> /dev/null && [ -e /usr/lib/libnss_wrapper.so ]; then
61-
export LD_PRELOAD='/usr/lib/libnss_wrapper.so'
62-
export NSS_WRAPPER_PASSWD="$(mktemp)"
63-
export NSS_WRAPPER_GROUP="$(mktemp)"
64-
echo "postgres:x:$(id -u):$(id -g):PostgreSQL:$PGDATA:/bin/false" > "$NSS_WRAPPER_PASSWD"
65-
echo "postgres:x:$(id -g):" > "$NSS_WRAPPER_GROUP"
66-
fi
58+
# initialize empty PGDATA directory with new database via 'initdb'
59+
init_pgdata() {
60+
# "initdb" is particular about the current user existing in "/etc/passwd", so we use "nss_wrapper" to fake that if necessary
61+
# see https://github.com/docker-library/postgres/pull/253, https://github.com/docker-library/postgres/issues/359, https://cwrap.org/nss_wrapper.html
62+
if ! getent passwd "$(id -u)" &> /dev/null && [ -e /usr/lib/libnss_wrapper.so ]; then
63+
export LD_PRELOAD='/usr/lib/libnss_wrapper.so'
64+
export NSS_WRAPPER_PASSWD="$(mktemp)"
65+
export NSS_WRAPPER_GROUP="$(mktemp)"
66+
echo "postgres:x:$(id -u):$(id -g):PostgreSQL:$PGDATA:/bin/false" > "$NSS_WRAPPER_PASSWD"
67+
echo "postgres:x:$(id -g):" > "$NSS_WRAPPER_GROUP"
68+
fi
6769

68-
file_env 'POSTGRES_USER' 'postgres'
69-
file_env 'POSTGRES_PASSWORD'
70+
file_env 'POSTGRES_INITDB_ARGS'
71+
if [ "$POSTGRES_INITDB_WALDIR" ]; then
72+
export POSTGRES_INITDB_ARGS="$POSTGRES_INITDB_ARGS --waldir $POSTGRES_INITDB_WALDIR"
73+
fi
7074

71-
file_env 'POSTGRES_INITDB_ARGS'
72-
if [ "$POSTGRES_INITDB_WALDIR" ]; then
73-
export POSTGRES_INITDB_ARGS="$POSTGRES_INITDB_ARGS --waldir $POSTGRES_INITDB_WALDIR"
74-
fi
75-
eval 'initdb --username="$POSTGRES_USER" --pwfile=<(echo "$POSTGRES_PASSWORD") '"$POSTGRES_INITDB_ARGS"
75+
eval 'initdb --username="$POSTGRES_USER" --pwfile=<(echo "$POSTGRES_PASSWORD") '"$POSTGRES_INITDB_ARGS"
7676

77-
# unset/cleanup "nss_wrapper" bits
78-
if [ "${LD_PRELOAD:-}" = '/usr/lib/libnss_wrapper.so' ]; then
79-
rm -f "$NSS_WRAPPER_PASSWD" "$NSS_WRAPPER_GROUP"
80-
unset LD_PRELOAD NSS_WRAPPER_PASSWD NSS_WRAPPER_GROUP
81-
fi
77+
# unset/cleanup "nss_wrapper" bits
78+
if [ "${LD_PRELOAD:-}" = '/usr/lib/libnss_wrapper.so' ]; then
79+
rm -f "$NSS_WRAPPER_PASSWD" "$NSS_WRAPPER_GROUP"
80+
unset LD_PRELOAD NSS_WRAPPER_PASSWD NSS_WRAPPER_GROUP
81+
fi
82+
}
8283

83-
# check password first so we can output the warning before postgres
84-
# messes it up
85-
if [ -n "$POSTGRES_PASSWORD" ]; then
86-
authMethod=md5
84+
# print large warning if POSTGRES_PASSWORD is empty
85+
print_password_warning() {
86+
# check password first so we can output the warning before postgres
87+
# messes it up
88+
if [ "${#POSTGRES_PASSWORD}" -ge 100 ]; then
89+
cat >&2 <<-'EOWARN'
8790
88-
if [ "${#POSTGRES_PASSWORD}" -ge 100 ]; then
89-
cat >&2 <<-'EOWARN'
91+
WARNING: The supplied POSTGRES_PASSWORD is 100+ characters.
9092
91-
WARNING: The supplied POSTGRES_PASSWORD is 100+ characters.
93+
This will not work if used via PGPASSWORD with "psql".
9294
93-
This will not work if used via PGPASSWORD with "psql".
95+
https://www.postgresql.org/message-id/flat/E1Rqxp2-0004Qt-PL%40wrigleys.postgresql.org (BUG #6412)
96+
https://github.com/docker-library/postgres/issues/507
9497
95-
https://www.postgresql.org/message-id/flat/E1Rqxp2-0004Qt-PL%40wrigleys.postgresql.org (BUG #6412)
96-
https://github.com/docker-library/postgres/issues/507
98+
EOWARN
99+
fi
100+
if [ -z "$POSTGRES_PASSWORD" ]; then
101+
# The - option suppresses leading tabs but *not* spaces. :)
102+
cat >&2 <<-'EOWARN'
103+
****************************************************
104+
WARNING: No password has been set for the database.
105+
This will allow anyone with access to the
106+
Postgres port to access your database. In
107+
Docker's default configuration, this is
108+
effectively any other container on the same
109+
system.
97110
98-
EOWARN
99-
fi
100-
else
101-
# The - option suppresses leading tabs but *not* spaces. :)
102-
cat >&2 <<-'EOWARN'
103-
****************************************************
104-
WARNING: No password has been set for the database.
105-
This will allow anyone with access to the
106-
Postgres port to access your database. In
107-
Docker's default configuration, this is
108-
effectively any other container on the same
109-
system.
110-
111-
Use "-e POSTGRES_PASSWORD=password" to set
112-
it in "docker run".
113-
****************************************************
114-
EOWARN
115-
116-
authMethod=trust
117-
fi
111+
Use "-e POSTGRES_PASSWORD=password" to set
112+
it in "docker run".
113+
****************************************************
114+
EOWARN
118115

119-
{
120-
echo
121-
echo "host all all all $authMethod"
122-
} >> "$PGDATA/pg_hba.conf"
116+
fi
117+
}
123118

124-
# internal start of server in order to allow set-up using psql-client
125-
# does not listen on external TCP/IP and waits until start finishes
126-
PGUSER="${PGUSER:-$POSTGRES_USER}" \
127-
pg_ctl -D "$PGDATA" \
128-
-o "-c listen_addresses=''" \
129-
-w start
119+
# run, source, or read files from /docker-entrypoint-initdb.d (or specified directory)
120+
process_init_files() {
121+
# psql here for backwards compatiblilty "${psql[@]}"
122+
psql=( psql_run )
130123

131-
file_env 'POSTGRES_DB' "$POSTGRES_USER"
124+
local initDir="${1:-/docker-entrypoint-initdb.d}"
132125

133-
export PGPASSWORD="${PGPASSWORD:-$POSTGRES_PASSWORD}"
134-
psql=( psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --no-password )
126+
echo
127+
for f in "${initDir%/}"/*; do
128+
case "$f" in
129+
*.sh)
130+
# https://github.com/docker-library/postgres/issues/450#issuecomment-393167936
131+
# https://github.com/docker-library/postgres/pull/452
132+
if [ -x "$f" ]; then
133+
echo "$0: running $f"
134+
"$f"
135+
else
136+
echo "$0: sourcing $f"
137+
. "$f"
138+
fi
139+
;;
140+
*.sql) echo "$0: running $f"; psql_run -f "$f"; echo ;;
141+
*.sql.gz) echo "$0: running $f"; gunzip -c "$f" | psql_run; echo ;;
142+
*) echo "$0: ignoring $f" ;;
143+
esac
144+
echo
145+
done
146+
}
135147

136-
if [ "$POSTGRES_DB" != 'postgres' ]; then
137-
"${psql[@]}" --dbname postgres --set db="$POSTGRES_DB" <<-'EOSQL'
138-
CREATE DATABASE :"db" ;
139-
EOSQL
140-
echo
141-
fi
142-
psql+=( --dbname "$POSTGRES_DB" )
148+
# run `psql` with proper arguments for user and db
149+
psql_run() {
150+
local query_runner=( psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --no-password )
151+
if [ -n "$POSTGRES_DB" ]; then
152+
query_runner+=( --dbname "$POSTGRES_DB" )
153+
fi
154+
155+
"${query_runner[@]}" "$@"
156+
}
143157

158+
# create initial postgresql superuser with password and database
159+
# uses environment variables for input: POSTGRES_USER, POSTGRES_PASSWORD, POSTGRES_DB
160+
setup_database() {
161+
if [ "$POSTGRES_DB" != 'postgres' ]; then
162+
POSTGRES_DB= psql_run --dbname postgres --set db="$POSTGRES_DB" <<-'EOSQL'
163+
CREATE DATABASE :"db" ;
164+
EOSQL
144165
echo
145-
for f in /docker-entrypoint-initdb.d/*; do
146-
case "$f" in
147-
*.sh)
148-
# https://github.com/docker-library/postgres/issues/450#issuecomment-393167936
149-
# https://github.com/docker-library/postgres/pull/452
150-
if [ -x "$f" ]; then
151-
echo "$0: running $f"
152-
"$f"
153-
else
154-
echo "$0: sourcing $f"
155-
. "$f"
156-
fi
157-
;;
158-
*.sql) echo "$0: running $f"; "${psql[@]}" -f "$f"; echo ;;
159-
*.sql.gz) echo "$0: running $f"; gunzip -c "$f" | "${psql[@]}"; echo ;;
160-
*) echo "$0: ignoring $f" ;;
161-
esac
162-
echo
163-
done
166+
fi
167+
}
164168

165-
PGUSER="${PGUSER:-$POSTGRES_USER}" \
166-
pg_ctl -D "$PGDATA" -m fast -w stop
169+
# get user/pass and db from env vars or via file
170+
setup_env_vars() {
171+
file_env 'POSTGRES_PASSWORD'
167172

168-
unset PGPASSWORD
173+
file_env 'POSTGRES_USER' 'postgres'
174+
file_env 'POSTGRES_DB' "$POSTGRES_USER"
175+
}
169176

177+
# append md5 or trust auth to pg_hba.conf based on existence of POSTGRES_PASSWORD
178+
setup_pg_hba() {
179+
local authMethod
180+
if [ "$POSTGRES_PASSWORD" ]; then
181+
authMethod='md5'
182+
else
183+
authMethod='trust'
184+
fi
185+
186+
{
170187
echo
171-
echo 'PostgreSQL init process complete; ready for start up.'
172-
echo
188+
echo "host all all all $authMethod"
189+
} >> "$PGDATA/pg_hba.conf"
190+
}
191+
192+
# start socket-only postgresql server for setting up user or running scripts
193+
temporary_pgserver_start() {
194+
# internal start of server in order to allow set-up using psql-client
195+
# does not listen on external TCP/IP and waits until start finishes
196+
PGUSER="${PGUSER:-$POSTGRES_USER}" \
197+
pg_ctl -D "$PGDATA" \
198+
-o "-c listen_addresses=''" \
199+
-w start
200+
#??? "$@"
201+
}
202+
203+
# stop postgresql server after done setting up user and running scripts
204+
temporary_pgserver_stop() {
205+
PGUSER="${PGUSER:-postgres}" \
206+
pg_ctl -D "$PGDATA" -m fast -w stop
207+
}
208+
209+
main() {
210+
# if first arg looks like a flag, assume we want to run postgres server
211+
if [ "${1:0:1}" = '-' ]; then
212+
set -- postgres "$@"
173213
fi
174-
fi
175214

176-
exec "$@"
215+
# setup data directories and permissions, then restart script as postgres user
216+
if [ "$1" = 'postgres' ] && [ "$(id -u)" = '0' ]; then
217+
create_postgres_dirs
218+
exec gosu postgres "$BASH_SOURCE" "$@"
219+
fi
220+
221+
if [ "$1" = 'postgres' ]; then
222+
create_postgres_dirs
223+
224+
# only run initialization on an empty data directory
225+
# look specifically for PG_VERSION, as it is expected in the DB dir
226+
if [ ! -s "$PGDATA/PG_VERSION" ]; then
227+
init_pgdata
228+
229+
setup_env_vars
230+
print_password_warning
231+
setup_pg_hba
232+
233+
# PGPASSWORD is required for psql when authentication is required for 'local' connections via pg_hba.conf and is otherwise harmless
234+
# e.g. when '--auth=md5' or '--auth-local=md5' is used in POSTGRES_INITDB_ARGS
235+
export PGPASSWORD="${PGPASSWORD:-$POSTGRES_PASSWORD}"
236+
temporary_pgserver_start
237+
238+
setup_database
239+
240+
process_init_files
241+
242+
temporary_pgserver_stop
243+
unset PGPASSWORD
244+
245+
echo
246+
echo 'PostgreSQL init process complete; ready for start up.'
247+
echo
248+
else
249+
echo
250+
echo 'PostgreSQL Database directory appears to contain a database; Skipping initialization'
251+
echo
252+
fi
253+
fi
254+
255+
exec "$@"
256+
}
257+
258+
if ! _is_sourced; then
259+
main "$@"
260+
fi

0 commit comments

Comments
 (0)