diff --git a/Dockerfile b/Dockerfile index f059ccc..ed9888a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -49,10 +49,6 @@ LABEL org.opencontainers.image.base.name="python:3.13-alpine" ENV TINI_VERSION=v0.19.0 -# Default runtime identity and umask (overridable) -ENV PUID=1000 -ENV PGID=1000 -ENV UMASK=022 # Runtime dependencies (smaller than build stage) RUN apk add --no-cache \ diff --git a/VERSION b/VERSION index c4f73e7..01a8ffe 100755 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -4.5.6-develop12 +4.5.6-develop13 diff --git a/entrypoint.sh b/entrypoint.sh index 2f02cde..9a4044a 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -1,101 +1,179 @@ #!/bin/bash - -# Exit on any error -set -e - -# Runtime identity and permissions -PUID="${PUID:-1000}" -PGID="${PGID:-1000}" -UMASK="${UMASK:-022}" - -# Validate inputs (numeric PUID/PGID) -if ! [[ "$PUID" =~ ^[0-9]+$ ]] || ! [[ "$PGID" =~ ^[0-9]+$ ]]; then - echo "ERROR: PUID and PGID must be numeric. Got PUID='$PUID' PGID='$PGID'" - exit 1 -fi - -umask "$UMASK" +set -euo pipefail # Exit on error, undefined vars, pipe failures # Configuration -SOURCE_FILE="/app/config/config.yml.sample" -DEST_FILE="/config/config.yml.sample" +readonly SOURCE_FILE="/app/config/config.yml.sample" +readonly DEST_FILE="/config/config.yml.sample" -# Function to safely copy with atomic operation +# Logging function for consistent output +log() { + echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >&2 +} + +# Validate numeric environment variables +validate_numeric_env() { + local var_name="$1" + local var_value="$2" + + if [[ -n "$var_value" ]] && ! [[ "$var_value" =~ ^[0-9]+$ ]]; then + log "Warning: $var_name must be numeric. Got $var_name='$var_value' - ignoring PUID/PGID and running as root" + return 1 + fi + return 0 +} + +# Validate and set PUID/PGID +validate_user_group_ids() { + local puid_valid=0 + local pgid_valid=0 + + if ! validate_numeric_env "PUID" "${PUID:-}"; then + puid_valid=1 + fi + + if ! validate_numeric_env "PGID" "${PGID:-}"; then + pgid_valid=1 + fi + + # If either is invalid, clear both + if [[ $puid_valid -eq 1 ]] || [[ $pgid_valid -eq 1 ]]; then + PUID="" + PGID="" + return 1 + fi + + return 0 +} + +# Safely copy file with atomic operation and error handling safe_copy() { local src="$1" local dest="$2" local temp_file="${dest}.tmp" - # Create a temporary file first - cp "$src" "$temp_file" + # Validate source file exists and is readable + if [[ ! -f "$src" ]] || [[ ! -r "$src" ]]; then + log "Error: Source file '$src' does not exist or is not readable" + return 1 + fi - # Atomic move to final destination - mv "$temp_file" "$dest" + # Create parent directory if it doesn't exist + local dest_dir + dest_dir="$(dirname "$dest")" + if [[ ! -d "$dest_dir" ]]; then + mkdir -p "$dest_dir" || { + log "Error: Could not create destination directory '$dest_dir'" + return 1 + } + fi - echo "Successfully copied $src to $dest" + # Atomic copy operation + if cp "$src" "$temp_file" && mv "$temp_file" "$dest"; then + log "Successfully copied $src to $dest" + return 0 + else + # Clean up temp file on failure + [[ -f "$temp_file" ]] && rm -f "$temp_file" + log "Error: Failed to copy $src to $dest" + return 1 + fi } -# Function to fix permissions (only when needed; quiet if no changes) +# Optimized permission fixing with better performance fix_permissions() { local path="$1" - local uid="${PUID:-1000}" - local gid="${PGID:-1000}" - if [ -d "$path" ]; then - if find "$path" -xdev \( -not -user "$uid" -o -not -group "$gid" \) -print -quit | grep -q .; then - if chown -R "$uid:$gid" "$path" 2>/dev/null; then - echo "Corrected ownership of directory $path to $uid:$gid" - else - echo "Warning: Could not change ownership of directory $path" - fi + # Skip if PUID or PGID are not set + if [[ -z "${PUID:-}" ]] || [[ -z "${PGID:-}" ]]; then + log "Skipping permission fix for $path - PUID or PGID not set" + return 0 + fi + + # Check if we're running as root + if [[ "$(id -u)" != "0" ]]; then + log "Skipping permission fix for $path - not running as root" + return 0 + fi + + local needs_fix=0 + + if [[ -d "$path" ]]; then + # Check if any files in directory need ownership change + if find "$path" -xdev \( -not -user "$PUID" -o -not -group "$PGID" \) -print -quit 2>/dev/null | grep -q .; then + needs_fix=1 + fi + elif [[ -e "$path" ]]; then + # Check if file needs ownership change + if [[ "$(stat -c '%u:%g' "$path" 2>/dev/null || echo "0:0")" != "$PUID:$PGID" ]]; then + needs_fix=1 fi else - if ! find "$path" -maxdepth 0 -user "$uid" -a -group "$gid" -print -quit | grep -q .; then - if chown "$uid:$gid" "$path" 2>/dev/null; then - echo "Corrected ownership of $path to $uid:$gid" - else - echo "Warning: Could not change ownership of $path" - fi + log "Warning: Path '$path' does not exist, skipping permission fix" + return 0 + fi + + if [[ $needs_fix -eq 1 ]]; then + if chown -R "$PUID:$PGID" "$path" 2>/dev/null; then + local type_msg="file" + [[ -d "$path" ]] && type_msg="directory" + log "Corrected ownership of $type_msg $path to $PUID:$PGID" + return 0 + else + log "Warning: Could not change ownership of $path" + return 1 fi fi + + return 0 +} + +# Execute command with appropriate privilege level +execute_command() { + local current_uid + current_uid="$(id -u)" + + if [[ "$current_uid" = "0" ]]; then + if [[ -n "${PUID:-}" ]] && [[ -n "${PGID:-}" ]]; then + log "Changing privileges to PUID:PGID = $PUID:$PGID" + exec /sbin/su-exec "${PUID}:${PGID}" "$@" || { + log "Warning: Could not drop privileges to ${PUID}:${PGID}, continuing as root" + exec "$@" + } + else + log "PUID/PGID not set, running as root" + exec "$@" + fi + else + log "Already running as non-root user (UID: $current_uid), executing command" + exec "$@" + fi } -# Main logic -if [ -d "/config" ]; then - if [ -f "$SOURCE_FILE" ] && [ -s "$SOURCE_FILE" ]; then - if [ ! -f "$DEST_FILE" ] || ! cmp -s "$SOURCE_FILE" "$DEST_FILE"; then - # Safely copy the file (logs only when copy occurs) - safe_copy "$SOURCE_FILE" "$DEST_FILE" - # Fix permissions (logs only if changes made) when running as root - if [ "$(id -u)" = "0" ]; then - fix_permissions "$DEST_FILE" +# Main execution +main() { + # Validate user/group IDs + validate_user_group_ids + + # Handle config file setup + if [[ -d "/config" ]]; then + if [[ -f "$SOURCE_FILE" ]] && [[ -s "$SOURCE_FILE" ]]; then + # Check if destination needs updating + if [[ ! -f "$DEST_FILE" ]] || ! cmp -s "$SOURCE_FILE" "$DEST_FILE" 2>/dev/null; then + if safe_copy "$SOURCE_FILE" "$DEST_FILE"; then + # Fix permissions if running as root and IDs are set + if [[ "$(id -u)" = "0" ]] && [[ -n "${PUID:-}" ]] && [[ -n "${PGID:-}" ]]; then + fix_permissions "$DEST_FILE" + fi + fi fi + elif [[ ! -f "$SOURCE_FILE" ]]; then + log "Warning: Source file $SOURCE_FILE does not exist, skipping config setup" fi - elif [ ! -f "$SOURCE_FILE" ]; then - echo "ERROR: Source file $SOURCE_FILE does not exist" - exit 1 fi -fi -# Set HOME if /config exists -if [ -d "/config" ]; then - export HOME=/config -fi + # Execute the main command + execute_command "$@" +} -# Execute the main command: -# - If running as root, drop privileges to PUID:PGID via su-exec -# - If already non-root (e.g., docker-compose sets user:), run as-is -set +e # Temporarily disable exit on error for su-exec handling -if [ "$(id -u)" = "0" ]; then - /sbin/su-exec "${PUID}:${PGID}" "$@" - if [ $? -eq 0 ]; then - # Won't reach here if su-exec succeeds - true - else - echo "Warning: Could not drop privileges to ${PUID}:${PGID}, continuing as root" - exec "$@" - fi -else - exec "$@" -fi -set -e # Re-enable exit on error +# Run main function with all arguments +main "$@"