Dispatcharr/docker/init/01-user-setup.sh
None 52ed0fc15a fix: run PostgreSQL as PUID/PGID user to resolve ownership conflicts (#1078)
PostgreSQL now runs as the PUID/PGID user ($POSTGRES_USER) instead of the internal postgres system user (UID 102). This fixes container startup failures when PUID/PGID is set, caused by chown permission errors on restricted filesystems (NFS root_squash, CIFS) and UID collisions with the postgres system user.

Changes:
- Run all PostgreSQL operations (initdb, pg_ctl, psql) as $POSTGRES_USER
- Auto-detect PUID/PGID from existing data owner when not explicitly set
- Validate PUID/PGID (reject zero, non-numeric values) before startup
- Migrate existing data ownership with sentinel-based skip optimization
- Use trust auth for local Unix sockets, md5 for network connections
- Add promote_app_role() and ensure_app_database() as idempotent startup guarantees that handle fresh installs, upgrades, and PUID changes
- Preserve postgres role as superuser for rollback compatibility
- Centralize /data/db ownership in 02-postgres.sh (sentinel-aware)
- Add integration test suite (20 scenarios) covering fresh installs, upgrades, restarts, PUID changes, UID collisions, bind mounts, modular mode, PG major upgrades, and end-to-end web UI verification
2026-03-11 23:36:32 -05:00

140 lines
6.3 KiB
Bash

#!/bin/bash
# NOTE: PUID/PGID values matching internal system UIDs (e.g. 102 for the
# postgres package user) will cause that OS user/group to be renamed to
# $POSTGRES_USER inside the container. This is cosmetic and does not affect
# runtime behavior since all postgres operations run as $POSTGRES_USER
# rather than the postgres system user.
# Auto-detect PUID/PGID from existing data when not explicitly set.
# Avoids a cross-UID chown on upgrade, which would fail on restricted
# filesystems (NFS root_squash, CIFS). UID/GID 0 is excluded — PostgreSQL
# refuses to run as root. Falls through to default 1000 for new installs.
if [ -z "${PUID+x}" ] && [ -f "${POSTGRES_DIR}/PG_VERSION" ]; then
_data_uid=$(stat -c '%u' "${POSTGRES_DIR}/PG_VERSION")
if [ "$_data_uid" -ne 0 ] 2>/dev/null; then
export PUID=$_data_uid
echo "PUID not set — defaulting to existing data owner UID: $PUID"
fi
fi
if [ -z "${PGID+x}" ] && [ -f "${POSTGRES_DIR}/PG_VERSION" ]; then
_data_gid=$(stat -c '%g' "${POSTGRES_DIR}/PG_VERSION")
if [ "$_data_gid" -ne 0 ] 2>/dev/null; then
export PGID=$_data_gid
echo "PGID not set — defaulting to existing data owner GID: $PGID"
fi
fi
export PUID=${PUID:-1000}
export PGID=${PGID:-1000}
# Validate PUID/PGID are positive integers before any user/group operations.
# Non-numeric values would cause useradd/groupadd to fail with confusing errors.
if ! [[ "$PUID" =~ ^[0-9]+$ ]] || ! [[ "$PGID" =~ ^[0-9]+$ ]]; then
echo ""
echo "================================================================"
echo "ERROR: PUID and PGID must be positive integers."
echo " PUID=$PUID PGID=$PGID"
echo " Please set valid numeric values (default: 1000)."
echo "================================================================"
echo ""
exit 1
fi
# PostgreSQL refuses to run as root (UID 0). Block early — before any
# user/group manipulation — to prevent renaming the root user/group,
# which would break the container.
if [ "$PUID" = "0" ] || [ "$PGID" = "0" ]; then
echo ""
echo "================================================================"
echo "ERROR: PUID=0 or PGID=0 is not supported."
echo " PostgreSQL cannot run as root (UID 0)."
echo " Please set PUID and PGID to a non-zero value (default: 1000)."
echo "================================================================"
echo ""
exit 1
fi
# Check if group with PGID exists
if getent group "$PGID" >/dev/null 2>&1; then
# Group exists, check if it's named correctly (should match POSTGRES_USER)
existing_group=$(getent group "$PGID" | cut -d: -f1)
if [ "$existing_group" != "$POSTGRES_USER" ]; then
# Rename the existing group to match POSTGRES_USER
groupmod -n "$POSTGRES_USER" "$existing_group"
echo "Group $existing_group with GID $PGID renamed to $POSTGRES_USER"
fi
else
# Group doesn't exist, create it with same name as POSTGRES_USER
groupadd -g "$PGID" "$POSTGRES_USER"
echo "Group $POSTGRES_USER with GID $PGID created"
fi
# Create user if it doesn't exist
if ! getent passwd $PUID > /dev/null 2>&1; then
useradd -u $PUID -g $PGID -m $POSTGRES_USER
else
existing_user=$(getent passwd $PUID | cut -d: -f1)
if [ "$existing_user" != "$POSTGRES_USER" ]; then
usermod -l $POSTGRES_USER -g $PGID "$existing_user"
fi
fi
# Get the GID of /dev/dri/renderD128 on the host (must be mounted into container)
if [ -e "/dev/dri/renderD128" ]; then
HOST_RENDER_GID=$(stat -c '%g' /dev/dri/renderD128)
# Check if this GID belongs to the video group
VIDEO_GID=$(getent group video 2>/dev/null | cut -d: -f3)
if [ "$HOST_RENDER_GID" = "$VIDEO_GID" ]; then
echo "RenderD128 GID ($HOST_RENDER_GID) matches video group GID. Using video group for GPU access."
# Make sure POSTGRES_USER is in video group
if ! id -nG "$POSTGRES_USER" | grep -qw "video"; then
usermod -a -G video "$POSTGRES_USER"
echo "Added user $POSTGRES_USER to video group for GPU access"
fi
else
# We need to ensure render group exists with correct GID
if getent group render >/dev/null; then
CURRENT_RENDER_GID=$(getent group render | cut -d: -f3)
if [ "$CURRENT_RENDER_GID" != "$HOST_RENDER_GID" ]; then
# Check if another group already has the target GID
if getent group "$HOST_RENDER_GID" >/dev/null 2>&1; then
EXISTING_GROUP=$(getent group "$HOST_RENDER_GID" | cut -d: -f1)
echo "Warning: Cannot change render group GID to $HOST_RENDER_GID as it's already used by group '$EXISTING_GROUP'"
# Add user to the existing group with the target GID to ensure device access
if ! id -nG "$POSTGRES_USER" | grep -qw "$EXISTING_GROUP"; then
usermod -a -G "$EXISTING_GROUP" "$POSTGRES_USER" || echo "Warning: Failed to add user to $EXISTING_GROUP group"
echo "Added user $POSTGRES_USER to $EXISTING_GROUP group for GPU access"
fi
else
echo "Changing render group GID from $CURRENT_RENDER_GID to $HOST_RENDER_GID"
groupmod -g "$HOST_RENDER_GID" render || echo "Warning: Failed to change render group GID. Continuing anyway..."
fi
fi
else
echo "Creating render group with GID $HOST_RENDER_GID"
groupadd -g "$HOST_RENDER_GID" render
fi
# Make sure POSTGRES_USER is in render group
if ! id -nG "$POSTGRES_USER" | grep -qw "render"; then
usermod -a -G render "$POSTGRES_USER"
echo "Added user $POSTGRES_USER to render group for GPU access"
fi
fi
else
echo "Warning: /dev/dri/renderD128 not found. GPU acceleration may not be available."
fi
# Always add user to video group for hardware acceleration if it exists
# (some systems use video group for general GPU access)
if getent group video >/dev/null 2>&1; then
if ! id -nG "$POSTGRES_USER" | grep -qw "video"; then
usermod -a -G video "$POSTGRES_USER"
echo "Added user $POSTGRES_USER to video group for hardware acceleration access"
fi
fi
# Run nginx as specified user (replace any existing user directive on line 1)
sed -i "1s/^user .*/user $POSTGRES_USER;/" /etc/nginx/nginx.conf