Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,8 @@ RUN apk update && apk upgrade && \
tzdata \
libedit \
libldap \
libcap && \
libcap \
su-exec && \
rm -rf /var/cache/apk/*

# Copy in the Python packages
Expand Down Expand Up @@ -197,7 +198,7 @@ RUN /venv/bin/python3 -m pip install --no-cache-dir gunicorn==23.0.0 && \
useradd -r -u 5050 -g root -s /sbin/nologin pgadmin && \
mkdir -p /run/pgadmin /var/lib/pgadmin && \
chown pgadmin:root /run/pgadmin /var/lib/pgadmin && \
chmod g=u /var/lib/pgadmin && \
chmod g=u /run/pgadmin /var/lib/pgadmin && \
touch /pgadmin4/config_distro.py && \
chown pgadmin:root /pgadmin4/config_distro.py && \
chmod g=u /pgadmin4/config_distro.py && \
Expand Down
123 changes: 101 additions & 22 deletions pkg/docker/entrypoint.sh
Original file line number Diff line number Diff line change
@@ -1,12 +1,78 @@
#!/usr/bin/env bash

# Fixup the passwd file, in case we're on OpenShift
if ! whoami > /dev/null 2>&1; then
if [ "$(id -u)" -ne 5050 ]; then
if [ -w /etc/passwd ]; then
echo "${USER_NAME:-pgadminr}:x:$(id -u):0:${USER_NAME:-pgadminr} user:${HOME}:/sbin/nologin" >> /etc/passwd
########################################################################
# PUID/PGID support
#
# When the container runs as root (e.g. --user root), the pgadmin user
# is reassigned to the requested UID/GID and all initialization +
# gunicorn run via su-exec as that user.
#
# When the container runs as non-root (default USER 5050, or OpenShift
# random UID), PUID/PGID are ignored and everything runs as the
# current user.
########################################################################

PUID=${PUID:-5050}
PGID=${PGID:-0}

# Validate PUID/PGID are numeric and in acceptable range
if ! echo "$PUID" | grep -qE '^[0-9]+$'; then
echo "ERROR: PUID must be a numeric value, got '$PUID'"
exit 1
fi
if ! echo "$PGID" | grep -qE '^[0-9]+$'; then
echo "ERROR: PGID must be a numeric value, got '$PGID'"
exit 1
fi
if [ "$PUID" -eq 0 ]; then
echo "ERROR: PUID=0 (root) is not allowed. Use a non-root UID."
exit 1
fi

if [ "$(id -u)" = "0" ]; then
# Ensure a group with the target GID exists
if ! getent group "$PGID" > /dev/null 2>&1; then
if ! addgroup -g "$PGID" pggroup; then
echo "ERROR: Failed to create group with GID=$PGID"
exit 1
fi
fi

# Reassign the pgadmin user to the desired UID/GID
if ! usermod -o -u "$PUID" -g "$PGID" pgadmin; then
echo "ERROR: Failed to set pgadmin user to UID=$PUID GID=$PGID"
exit 1
fi

# Fix ownership of runtime directories BEFORE any initialization
for dir in /run/pgadmin /var/lib/pgadmin; do
if [ -d "$dir" ]; then
chown -R "$PUID:$PGID" "$dir"
fi
done

# Fix ownership of individual files (no -R needed)
if [ -e /pgadmin4/config_distro.py ]; then
chown "$PUID:$PGID" /pgadmin4/config_distro.py
fi

if [ -d /certs ]; then
chown -R "$PUID:$PGID" /certs
fi

SU_EXEC="su-exec $PUID:$PGID"
echo "pgAdmin will run as UID=$PUID, GID=$PGID"
else
SU_EXEC=""

# Fixup the passwd file, in case we're on OpenShift
if ! whoami > /dev/null 2>&1; then
if [ "$(id -u)" -ne 5050 ]; then
if [ -w /etc/passwd ]; then
echo "${USER_NAME:-pgadminr}:x:$(id -u):0:${USER_NAME:-pgadminr} user:${HOME}:/sbin/nologin" >> /etc/passwd
fi
fi
fi
fi
fi

# usage: file_env VAR [DEFAULT] ie: file_env 'XYZ_DB_PASSWORD' 'example'
Expand Down Expand Up @@ -74,12 +140,17 @@ EOF
esac
echo "${var#PGADMIN_CONFIG_} = $val" >> "${CONFIG_DISTRO_FILE_PATH}"
done

# If running as root with custom config distro path, fix ownership
if [ "$(id -u)" = "0" ] && [ "${CONFIG_DISTRO_FILE_PATH}" != "/pgadmin4/config_distro.py" ]; then
chown "$PUID:$PGID" "${CONFIG_DISTRO_FILE_PATH}"
fi
fi

# Check whether the external configuration database exists if it is being used.
external_config_db_exists="False"
if [ -n "${PGADMIN_CONFIG_CONFIG_DATABASE_URI}" ]; then
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)")
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)")
fi

# DRY of the code to load the PGADMIN_SERVER_JSON_FILE
Expand All @@ -96,9 +167,9 @@ function load_server_json_file() {
# When running in Desktop mode, no user is created
# so we have to import servers anonymously
if [ "${PGADMIN_CONFIG_SERVER_MODE}" = "False" ]; then
/venv/bin/python3 /pgadmin4/setup.py load-servers "${PGADMIN_SERVER_JSON_FILE}" ${EXTRA_ARGS}
$SU_EXEC /venv/bin/python3 /pgadmin4/setup.py load-servers "${PGADMIN_SERVER_JSON_FILE}" ${EXTRA_ARGS}
else
/venv/bin/python3 /pgadmin4/setup.py load-servers "${PGADMIN_SERVER_JSON_FILE}" --user "${PGADMIN_DEFAULT_EMAIL}" ${EXTRA_ARGS}
$SU_EXEC /venv/bin/python3 /pgadmin4/setup.py load-servers "${PGADMIN_SERVER_JSON_FILE}" --user "${PGADMIN_DEFAULT_EMAIL}" ${EXTRA_ARGS}
fi
fi
}
Expand All @@ -124,7 +195,7 @@ if [ ! -f /var/lib/pgadmin/pgadmin4.db ] && [ "${external_config_db_exists}" = "
fi
email_config="{'CHECK_EMAIL_DELIVERABILITY': ${CHECK_EMAIL_DELIVERABILITY}, 'ALLOW_SPECIAL_EMAIL_DOMAINS': ${ALLOW_SPECIAL_EMAIL_DOMAINS}, 'GLOBALLY_DELIVERABLE': ${GLOBALLY_DELIVERABLE}}"
echo "email config is ${email_config}"
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)")
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)")
if echo "${is_valid_email}" | grep "False" > /dev/null; then
echo "'${PGADMIN_DEFAULT_EMAIL}' does not appear to be a valid email address. Please reset the PGADMIN_DEFAULT_EMAIL environment variable and try again."
echo "Validation output: ${is_valid_email}"
Expand All @@ -140,7 +211,7 @@ if [ ! -f /var/lib/pgadmin/pgadmin4.db ] && [ "${external_config_db_exists}" = "

# Initialize DB before starting Gunicorn
# Importing pgadmin4 (from this script) is enough
/venv/bin/python3 run_pgadmin.py
$SU_EXEC /venv/bin/python3 run_pgadmin.py

export PGADMIN_PREFERENCES_JSON_FILE="${PGADMIN_PREFERENCES_JSON_FILE:-/pgadmin4/preferences.json}"

Expand All @@ -150,22 +221,30 @@ if [ ! -f /var/lib/pgadmin/pgadmin4.db ] && [ "${external_config_db_exists}" = "
# Pre-load any required preferences
if [ -f "${PGADMIN_PREFERENCES_JSON_FILE}" ]; then
if [ "${PGADMIN_CONFIG_SERVER_MODE}" = "False" ]; then
DESKTOP_USER=$(cd /pgadmin4 && /venv/bin/python3 -c 'import config; print(config.DESKTOP_USER)')
/venv/bin/python3 /pgadmin4/setup.py set-prefs "${DESKTOP_USER}" --input-file "${PGADMIN_PREFERENCES_JSON_FILE}"
DESKTOP_USER=$(cd /pgadmin4 && $SU_EXEC /venv/bin/python3 -c 'import config; print(config.DESKTOP_USER)')
$SU_EXEC /venv/bin/python3 /pgadmin4/setup.py set-prefs "${DESKTOP_USER}" --input-file "${PGADMIN_PREFERENCES_JSON_FILE}"
else
/venv/bin/python3 /pgadmin4/setup.py set-prefs "${PGADMIN_DEFAULT_EMAIL}" --input-file "${PGADMIN_PREFERENCES_JSON_FILE}"
$SU_EXEC /venv/bin/python3 /pgadmin4/setup.py set-prefs "${PGADMIN_DEFAULT_EMAIL}" --input-file "${PGADMIN_PREFERENCES_JSON_FILE}"
fi
fi
# Copy the pgpass file passed using secrets
if [ -f "${PGPASS_FILE}" ]; then
if [ -n "${PGPASS_FILE}" ] && [ -f "${PGPASS_FILE}" ]; then
if [ "${PGADMIN_CONFIG_SERVER_MODE}" = "False" ]; then
cp ${PGPASS_FILE} /var/lib/pgadmin/.pgpass
cp "${PGPASS_FILE}" /var/lib/pgadmin/.pgpass
chmod 600 /var/lib/pgadmin/.pgpass
# Fix ownership when running as root
if [ "$(id -u)" = "0" ]; then
chown "$PUID:$PGID" /var/lib/pgadmin/.pgpass
fi
else
PGADMIN_USER_CONFIG_DIR=$(echo "${PGADMIN_DEFAULT_EMAIL}" | sed 's/@/_/g')
mkdir -p /var/lib/pgadmin/storage/${PGADMIN_USER_CONFIG_DIR}
cp ${PGPASS_FILE} /var/lib/pgadmin/storage/${PGADMIN_USER_CONFIG_DIR}/.pgpass
chmod 600 /var/lib/pgadmin/storage/${PGADMIN_USER_CONFIG_DIR}/.pgpass
mkdir -p "/var/lib/pgadmin/storage/${PGADMIN_USER_CONFIG_DIR}"
cp "${PGPASS_FILE}" "/var/lib/pgadmin/storage/${PGADMIN_USER_CONFIG_DIR}/.pgpass"
chmod 600 "/var/lib/pgadmin/storage/${PGADMIN_USER_CONFIG_DIR}/.pgpass"
# Fix ownership when running as root
if [ "$(id -u)" = "0" ]; then
chown -R "$PUID:$PGID" "/var/lib/pgadmin/storage/${PGADMIN_USER_CONFIG_DIR}"
fi
fi
fi
# If already initialised and PGADMIN_REPLACE_SERVERS_ON_STARTUP is set to true, then load the server json file.
Expand All @@ -180,7 +259,7 @@ fi

# Get the session timeout from the pgAdmin config. We'll use this (in seconds)
# to define the Gunicorn worker timeout
TIMEOUT=$(cd /pgadmin4 && /venv/bin/python3 -c 'import config; print(config.SESSION_EXPIRATION_TIME * 60 * 60 * 24)')
TIMEOUT=$(cd /pgadmin4 && $SU_EXEC /venv/bin/python3 -c 'import config; print(config.SESSION_EXPIRATION_TIME * 60 * 60 * 24)')

# NOTE: currently pgadmin can run only with 1 worker due to sessions implementation
# Using --threads to have multi-threaded single-process worker
Expand All @@ -196,7 +275,7 @@ else
fi

if [ -n "${PGADMIN_ENABLE_TLS}" ]; then
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
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
else
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
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
fi
Loading