11#! /usr/bin/env bash
22
3- # Fixup the passwd file, in case we're on OpenShift
4- if ! whoami > /dev/null 2>&1 ; then
5- if [ " $( id -u) " -ne 5050 ]; then
6- if [ -w /etc/passwd ]; then
7- echo " ${USER_NAME:- pgadminr} :x:$( id -u) :0:${USER_NAME:- pgadminr} user:${HOME} :/sbin/nologin" >> /etc/passwd
3+ # #######################################################################
4+ # PUID/PGID support
5+ #
6+ # When the container runs as root (e.g. --user root), the pgadmin user
7+ # is reassigned to the requested UID/GID and all initialization +
8+ # gunicorn run via su-exec as that user.
9+ #
10+ # When the container runs as non-root (default USER 5050, or OpenShift
11+ # random UID), PUID/PGID are ignored and everything runs as the
12+ # current user.
13+ # #######################################################################
14+
15+ PUID=${PUID:- 5050}
16+ PGID=${PGID:- 0}
17+
18+ # Validate PUID/PGID are numeric and in acceptable range
19+ if ! echo " $PUID " | grep -qE ' ^[0-9]+$' ; then
20+ echo " ERROR: PUID must be a numeric value, got '$PUID '"
21+ exit 1
22+ fi
23+ if ! echo " $PGID " | grep -qE ' ^[0-9]+$' ; then
24+ echo " ERROR: PGID must be a numeric value, got '$PGID '"
25+ exit 1
26+ fi
27+ if [ " $PUID " -eq 0 ]; then
28+ echo " ERROR: PUID=0 (root) is not allowed. Use a non-root UID."
29+ exit 1
30+ fi
31+
32+ if [ " $( id -u) " = " 0" ]; then
33+ # Ensure a group with the target GID exists
34+ if ! getent group " $PGID " > /dev/null 2>&1 ; then
35+ if ! addgroup -g " $PGID " pggroup; then
36+ echo " ERROR: Failed to create group with GID=$PGID "
37+ exit 1
38+ fi
39+ fi
40+
41+ # Reassign the pgadmin user to the desired UID/GID
42+ if ! usermod -o -u " $PUID " -g " $PGID " pgadmin; then
43+ echo " ERROR: Failed to set pgadmin user to UID=$PUID GID=$PGID "
44+ exit 1
45+ fi
46+
47+ # Fix ownership of runtime directories BEFORE any initialization
48+ for dir in /run/pgadmin /var/lib/pgadmin; do
49+ if [ -d " $dir " ]; then
50+ chown -R " $PUID :$PGID " " $dir "
51+ fi
52+ done
53+
54+ # Fix ownership of individual files (no -R needed)
55+ if [ -e /pgadmin4/config_distro.py ]; then
56+ chown " $PUID :$PGID " /pgadmin4/config_distro.py
57+ fi
58+
59+ if [ -d /certs ]; then
60+ chown -R " $PUID :$PGID " /certs
61+ fi
62+
63+ SU_EXEC=" su-exec $PUID :$PGID "
64+ echo " pgAdmin will run as UID=$PUID , GID=$PGID "
65+ else
66+ SU_EXEC=" "
67+
68+ # Fixup the passwd file, in case we're on OpenShift
69+ if ! whoami > /dev/null 2>&1 ; then
70+ if [ " $( id -u) " -ne 5050 ]; then
71+ if [ -w /etc/passwd ]; then
72+ echo " ${USER_NAME:- pgadminr} :x:$( id -u) :0:${USER_NAME:- pgadminr} user:${HOME} :/sbin/nologin" >> /etc/passwd
73+ fi
74+ fi
875 fi
9- fi
1076fi
1177
1278# usage: file_env VAR [DEFAULT] ie: file_env 'XYZ_DB_PASSWORD' 'example'
74140 esac
75141 echo " ${var# PGADMIN_CONFIG_} = $val " >> " ${CONFIG_DISTRO_FILE_PATH} "
76142 done
143+
144+ # If running as root with custom config distro path, fix ownership
145+ if [ " $( id -u) " = " 0" ] && [ " ${CONFIG_DISTRO_FILE_PATH} " != " /pgadmin4/config_distro.py" ]; then
146+ chown " $PUID :$PGID " " ${CONFIG_DISTRO_FILE_PATH} "
147+ fi
77148fi
78149
79150# Check whether the external configuration database exists if it is being used.
80151external_config_db_exists=" False"
81152if [ -n " ${PGADMIN_CONFIG_CONFIG_DATABASE_URI} " ]; then
82- external_config_db_exists=$( cd /pgadmin4/pgadmin/utils && /venv/bin/python3 -c " from check_external_config_db import check_external_config_db; val = check_external_config_db(" ${PGADMIN_CONFIG_CONFIG_DATABASE_URI} " ); print(val)" )
153+ external_config_db_exists=$( cd /pgadmin4/pgadmin/utils && $SU_EXEC /venv/bin/python3 -c " from check_external_config_db import check_external_config_db; val = check_external_config_db(\ "${PGADMIN_CONFIG_CONFIG_DATABASE_URI} \ " ); print(val)" )
83154fi
84155
85156# DRY of the code to load the PGADMIN_SERVER_JSON_FILE
@@ -96,9 +167,9 @@ function load_server_json_file() {
96167 # When running in Desktop mode, no user is created
97168 # so we have to import servers anonymously
98169 if [ " ${PGADMIN_CONFIG_SERVER_MODE} " = " False" ]; then
99- /venv/bin/python3 /pgadmin4/setup.py load-servers " ${PGADMIN_SERVER_JSON_FILE} " ${EXTRA_ARGS}
170+ $SU_EXEC /venv/bin/python3 /pgadmin4/setup.py load-servers " ${PGADMIN_SERVER_JSON_FILE} " ${EXTRA_ARGS}
100171 else
101- /venv/bin/python3 /pgadmin4/setup.py load-servers " ${PGADMIN_SERVER_JSON_FILE} " --user " ${PGADMIN_DEFAULT_EMAIL} " ${EXTRA_ARGS}
172+ $SU_EXEC /venv/bin/python3 /pgadmin4/setup.py load-servers " ${PGADMIN_SERVER_JSON_FILE} " --user " ${PGADMIN_DEFAULT_EMAIL} " ${EXTRA_ARGS}
102173 fi
103174 fi
104175}
@@ -124,7 +195,7 @@ if [ ! -f /var/lib/pgadmin/pgadmin4.db ] && [ "${external_config_db_exists}" = "
124195 fi
125196 email_config=" {'CHECK_EMAIL_DELIVERABILITY': ${CHECK_EMAIL_DELIVERABILITY} , 'ALLOW_SPECIAL_EMAIL_DOMAINS': ${ALLOW_SPECIAL_EMAIL_DOMAINS} , 'GLOBALLY_DELIVERABLE': ${GLOBALLY_DELIVERABLE} }"
126197 echo " email config is ${email_config} "
127- is_valid_email=$( cd /pgadmin4/pgadmin/utils && /venv/bin/python3 -c " from validation_utils import validate_email; val = validate_email('${PGADMIN_DEFAULT_EMAIL} ', ${email_config} ); print(val)" )
198+ is_valid_email=$( cd /pgadmin4/pgadmin/utils && $SU_EXEC /venv/bin/python3 -c " from validation_utils import validate_email; val = validate_email('${PGADMIN_DEFAULT_EMAIL} ', ${email_config} ); print(val)" )
128199 if echo " ${is_valid_email} " | grep " False" > /dev/null; then
129200 echo " '${PGADMIN_DEFAULT_EMAIL} ' does not appear to be a valid email address. Please reset the PGADMIN_DEFAULT_EMAIL environment variable and try again."
130201 echo " Validation output: ${is_valid_email} "
@@ -140,7 +211,7 @@ if [ ! -f /var/lib/pgadmin/pgadmin4.db ] && [ "${external_config_db_exists}" = "
140211
141212 # Initialize DB before starting Gunicorn
142213 # Importing pgadmin4 (from this script) is enough
143- /venv/bin/python3 run_pgadmin.py
214+ $SU_EXEC /venv/bin/python3 run_pgadmin.py
144215
145216 export PGADMIN_PREFERENCES_JSON_FILE=" ${PGADMIN_PREFERENCES_JSON_FILE:-/ pgadmin4/ preferences.json} "
146217
@@ -150,22 +221,30 @@ if [ ! -f /var/lib/pgadmin/pgadmin4.db ] && [ "${external_config_db_exists}" = "
150221 # Pre-load any required preferences
151222 if [ -f " ${PGADMIN_PREFERENCES_JSON_FILE} " ]; then
152223 if [ " ${PGADMIN_CONFIG_SERVER_MODE} " = " False" ]; then
153- DESKTOP_USER=$( cd /pgadmin4 && /venv/bin/python3 -c ' import config; print(config.DESKTOP_USER)' )
154- /venv/bin/python3 /pgadmin4/setup.py set-prefs " ${DESKTOP_USER} " --input-file " ${PGADMIN_PREFERENCES_JSON_FILE} "
224+ DESKTOP_USER=$( cd /pgadmin4 && $SU_EXEC /venv/bin/python3 -c ' import config; print(config.DESKTOP_USER)' )
225+ $SU_EXEC /venv/bin/python3 /pgadmin4/setup.py set-prefs " ${DESKTOP_USER} " --input-file " ${PGADMIN_PREFERENCES_JSON_FILE} "
155226 else
156- /venv/bin/python3 /pgadmin4/setup.py set-prefs " ${PGADMIN_DEFAULT_EMAIL} " --input-file " ${PGADMIN_PREFERENCES_JSON_FILE} "
227+ $SU_EXEC /venv/bin/python3 /pgadmin4/setup.py set-prefs " ${PGADMIN_DEFAULT_EMAIL} " --input-file " ${PGADMIN_PREFERENCES_JSON_FILE} "
157228 fi
158229 fi
159230 # Copy the pgpass file passed using secrets
160- if [ -f " ${PGPASS_FILE} " ]; then
231+ if [ -n " ${PGPASS_FILE} " ] && [ - f " ${PGPASS_FILE} " ]; then
161232 if [ " ${PGADMIN_CONFIG_SERVER_MODE} " = " False" ]; then
162- cp ${PGPASS_FILE} /var/lib/pgadmin/.pgpass
233+ cp " ${PGPASS_FILE} " /var/lib/pgadmin/.pgpass
163234 chmod 600 /var/lib/pgadmin/.pgpass
235+ # Fix ownership when running as root
236+ if [ " $( id -u) " = " 0" ]; then
237+ chown " $PUID :$PGID " /var/lib/pgadmin/.pgpass
238+ fi
164239 else
165240 PGADMIN_USER_CONFIG_DIR=$( echo " ${PGADMIN_DEFAULT_EMAIL} " | sed ' s/@/_/g' )
166- mkdir -p /var/lib/pgadmin/storage/${PGADMIN_USER_CONFIG_DIR}
167- cp ${PGPASS_FILE} /var/lib/pgadmin/storage/${PGADMIN_USER_CONFIG_DIR} /.pgpass
168- chmod 600 /var/lib/pgadmin/storage/${PGADMIN_USER_CONFIG_DIR} /.pgpass
241+ mkdir -p " /var/lib/pgadmin/storage/${PGADMIN_USER_CONFIG_DIR} "
242+ cp " ${PGPASS_FILE} " " /var/lib/pgadmin/storage/${PGADMIN_USER_CONFIG_DIR} /.pgpass"
243+ chmod 600 " /var/lib/pgadmin/storage/${PGADMIN_USER_CONFIG_DIR} /.pgpass"
244+ # Fix ownership when running as root
245+ if [ " $( id -u) " = " 0" ]; then
246+ chown -R " $PUID :$PGID " " /var/lib/pgadmin/storage/${PGADMIN_USER_CONFIG_DIR} "
247+ fi
169248 fi
170249 fi
171250# If already initialised and PGADMIN_REPLACE_SERVERS_ON_STARTUP is set to true, then load the server json file.
180259
181260# Get the session timeout from the pgAdmin config. We'll use this (in seconds)
182261# to define the Gunicorn worker timeout
183- TIMEOUT=$( cd /pgadmin4 && /venv/bin/python3 -c ' import config; print(config.SESSION_EXPIRATION_TIME * 60 * 60 * 24)' )
262+ TIMEOUT=$( cd /pgadmin4 && $SU_EXEC /venv/bin/python3 -c ' import config; print(config.SESSION_EXPIRATION_TIME * 60 * 60 * 24)' )
184263
185264# NOTE: currently pgadmin can run only with 1 worker due to sessions implementation
186265# Using --threads to have multi-threaded single-process worker
196275fi
197276
198277if [ -n " ${PGADMIN_ENABLE_TLS} " ]; then
199- exec /venv/bin/gunicorn --limit-request-line " ${GUNICORN_LIMIT_REQUEST_LINE:- 8190} " --timeout " ${TIMEOUT} " --bind " ${BIND_ADDRESS} " -w 1 --threads " ${GUNICORN_THREADS:- 25} " --access-logfile " ${GUNICORN_ACCESS_LOGFILE:- -} " --keyfile /certs/server.key --certfile /certs/server.cert -c gunicorn_config.py run_pgadmin:app
278+ exec $SU_EXEC /venv/bin/gunicorn --limit-request-line " ${GUNICORN_LIMIT_REQUEST_LINE:- 8190} " --timeout " ${TIMEOUT} " --bind " ${BIND_ADDRESS} " -w 1 --threads " ${GUNICORN_THREADS:- 25} " --access-logfile " ${GUNICORN_ACCESS_LOGFILE:- -} " --keyfile /certs/server.key --certfile /certs/server.cert -c gunicorn_config.py run_pgadmin:app
200279else
201- exec /venv/bin/gunicorn --limit-request-line " ${GUNICORN_LIMIT_REQUEST_LINE:- 8190} " --limit-request-fields " ${GUNICORN_LIMIT_REQUEST_FIELDS:- 100} " --limit-request-field_size " ${GUNICORN_LIMIT_REQUEST_FIELD_SIZE:- 8190} " --timeout " ${TIMEOUT} " --bind " ${BIND_ADDRESS} " -w 1 --threads " ${GUNICORN_THREADS:- 25} " --access-logfile " ${GUNICORN_ACCESS_LOGFILE:- -} " -c gunicorn_config.py run_pgadmin:app
280+ exec $SU_EXEC /venv/bin/gunicorn --limit-request-line " ${GUNICORN_LIMIT_REQUEST_LINE:- 8190} " --limit-request-fields " ${GUNICORN_LIMIT_REQUEST_FIELDS:- 100} " --limit-request-field_size " ${GUNICORN_LIMIT_REQUEST_FIELD_SIZE:- 8190} " --timeout " ${TIMEOUT} " --bind " ${BIND_ADDRESS} " -w 1 --threads " ${GUNICORN_THREADS:- 25} " --access-logfile " ${GUNICORN_ACCESS_LOGFILE:- -} " -c gunicorn_config.py run_pgadmin:app
202281fi
0 commit comments