entrypoint.sh

Download
bash 304 lines 8.0 KB
  1#!/usr/bin/env bash
  2set -euo pipefail
  3
  4# Docker Entrypoint Script
  5# Handles initialization, configuration, and graceful shutdown for containerized applications
  6
  7# ============================================================================
  8# Configuration
  9# ============================================================================
 10
 11APP_NAME="${APP_NAME:-myapp}"
 12APP_ENV="${APP_ENV:-production}"
 13CONFIG_DIR="${CONFIG_DIR:-/app/config}"
 14DATA_DIR="${DATA_DIR:-/app/data}"
 15
 16# Colors (for logs)
 17RED='\033[0;31m'
 18GREEN='\033[0;32m'
 19YELLOW='\033[1;33m'
 20BLUE='\033[0;34m'
 21NC='\033[0m'
 22
 23# ============================================================================
 24# Logging Functions
 25# ============================================================================
 26
 27log_info() {
 28    echo -e "${BLUE}[$(date '+%Y-%m-%d %H:%M:%S')] [INFO]${NC} $*"
 29}
 30
 31log_success() {
 32    echo -e "${GREEN}[$(date '+%Y-%m-%d %H:%M:%S')] [SUCCESS]${NC} $*"
 33}
 34
 35log_error() {
 36    echo -e "${RED}[$(date '+%Y-%m-%d %H:%M:%S')] [ERROR]${NC} $*" >&2
 37}
 38
 39log_warning() {
 40    echo -e "${YELLOW}[$(date '+%Y-%m-%d %H:%M:%S')] [WARNING]${NC} $*"
 41}
 42
 43# ============================================================================
 44# Environment Variable Validation
 45# ============================================================================
 46
 47validate_required_vars() {
 48    log_info "Validating required environment variables..."
 49
 50    local required_vars=(
 51        "APP_NAME"
 52        "APP_ENV"
 53    )
 54
 55    local missing_vars=()
 56
 57    for var in "${required_vars[@]}"; do
 58        if [[ -z "${!var:-}" ]]; then
 59            missing_vars+=("$var")
 60        else
 61            log_info "  ✓ $var=${!var}"
 62        fi
 63    done
 64
 65    if [[ ${#missing_vars[@]} -gt 0 ]]; then
 66        log_error "Missing required environment variables:"
 67        for var in "${missing_vars[@]}"; do
 68            log_error "  - $var"
 69        done
 70        exit 1
 71    fi
 72
 73    log_success "All required variables present"
 74}
 75
 76validate_optional_vars() {
 77    log_info "Optional configuration:"
 78
 79    local optional_vars=(
 80        "DATABASE_URL"
 81        "REDIS_URL"
 82        "LOG_LEVEL"
 83        "MAX_WORKERS"
 84    )
 85
 86    for var in "${optional_vars[@]}"; do
 87        if [[ -n "${!var:-}" ]]; then
 88            # Mask sensitive values
 89            local display_value="${!var}"
 90            if [[ "$var" =~ URL ]]; then
 91                display_value=$(echo "${!var}" | sed 's/:[^@]*@/:***@/')
 92            fi
 93            log_info "  ✓ $var=$display_value"
 94        fi
 95    done
 96}
 97
 98# ============================================================================
 99# Configuration Template Processing
100# ============================================================================
101
102process_templates() {
103    log_info "Processing configuration templates..."
104
105    local template_dir="/app/templates"
106
107    if [[ ! -d "$template_dir" ]]; then
108        log_warning "Template directory not found: $template_dir"
109        return 0
110    fi
111
112    # Create config directory if it doesn't exist
113    mkdir -p "$CONFIG_DIR"
114
115    # Process all .template files
116    find "$template_dir" -name "*.template" -type f | while read -r template_file; do
117        local config_file
118        config_file="$CONFIG_DIR/$(basename "${template_file%.template}")"
119
120        log_info "  Processing: $(basename "$template_file") -> $(basename "$config_file")"
121
122        # Use envsubst to replace environment variables
123        envsubst < "$template_file" > "$config_file"
124
125        # Set appropriate permissions
126        chmod 644 "$config_file"
127    done
128
129    log_success "Configuration templates processed"
130}
131
132# ============================================================================
133# Wait for Dependencies
134# ============================================================================
135
136wait_for_service() {
137    local host=$1
138    local port=$2
139    local timeout=${3:-30}
140
141    log_info "Waiting for $host:$port (timeout: ${timeout}s)..."
142
143    local start_time
144    start_time=$(date +%s)
145
146    while ! nc -z "$host" "$port" 2>/dev/null; do
147        local current_time
148        current_time=$(date +%s)
149        local elapsed=$((current_time - start_time))
150
151        if [[ $elapsed -ge $timeout ]]; then
152            log_error "Timeout waiting for $host:$port"
153            return 1
154        fi
155
156        sleep 1
157    done
158
159    log_success "$host:$port is available"
160}
161
162wait_for_dependencies() {
163    log_info "Waiting for service dependencies..."
164
165    # Parse DATABASE_URL if present
166    if [[ -n "${DATABASE_URL:-}" ]]; then
167        # Extract host and port from URL (simplified)
168        # Example: postgresql://user:pass@host:5432/db
169        local db_host
170        local db_port
171
172        db_host=$(echo "$DATABASE_URL" | sed -n 's/.*@\([^:]*\):.*/\1/p')
173        db_port=$(echo "$DATABASE_URL" | sed -n 's/.*:\([0-9]*\)\/.*/\1/p')
174
175        if [[ -n "$db_host" ]] && [[ -n "$db_port" ]]; then
176            wait_for_service "$db_host" "$db_port" 60
177        fi
178    fi
179
180    # Parse REDIS_URL if present
181    if [[ -n "${REDIS_URL:-}" ]]; then
182        local redis_host
183        local redis_port
184
185        redis_host=$(echo "$REDIS_URL" | sed -n 's/.*@\([^:]*\):.*/\1/p')
186        redis_port=$(echo "$REDIS_URL" | sed -n 's/.*:\([0-9]*\).*/\1/p')
187
188        # Default Redis port if not specified
189        redis_port=${redis_port:-6379}
190
191        if [[ -n "$redis_host" ]]; then
192            wait_for_service "$redis_host" "$redis_port" 30
193        fi
194    fi
195
196    log_success "All dependencies are ready"
197}
198
199# ============================================================================
200# Signal Handling for Graceful Shutdown
201# ============================================================================
202
203cleanup() {
204    log_warning "Received shutdown signal, cleaning up..."
205
206    # If a child process is running, send it SIGTERM
207    if [[ -n "${APP_PID:-}" ]]; then
208        log_info "Sending SIGTERM to application (PID: $APP_PID)"
209        kill -TERM "$APP_PID" 2>/dev/null || true
210
211        # Wait for graceful shutdown (max 30 seconds)
212        local timeout=30
213        local elapsed=0
214
215        while kill -0 "$APP_PID" 2>/dev/null && [[ $elapsed -lt $timeout ]]; do
216            sleep 1
217            elapsed=$((elapsed + 1))
218        done
219
220        # Force kill if still running
221        if kill -0 "$APP_PID" 2>/dev/null; then
222            log_warning "Application didn't stop gracefully, forcing shutdown"
223            kill -KILL "$APP_PID" 2>/dev/null || true
224        fi
225    fi
226
227    log_success "Cleanup complete"
228    exit 0
229}
230
231# Trap signals for graceful shutdown
232trap cleanup SIGTERM SIGINT SIGQUIT
233
234# ============================================================================
235# Application Initialization
236# ============================================================================
237
238initialize_app() {
239    log_info "Initializing application..."
240
241    # Create necessary directories
242    mkdir -p "$DATA_DIR" "$CONFIG_DIR"
243
244    # Set permissions
245    chmod 755 "$DATA_DIR" "$CONFIG_DIR"
246
247    # Run any database migrations (if needed)
248    if [[ -x "/app/bin/migrate" ]]; then
249        log_info "Running database migrations..."
250        /app/bin/migrate
251    fi
252
253    log_success "Application initialized"
254}
255
256# ============================================================================
257# Main Entry Point
258# ============================================================================
259
260main() {
261    log_info "=== Starting $APP_NAME ($APP_ENV) ==="
262    echo
263
264    # Step 1: Validate environment
265    validate_required_vars
266    validate_optional_vars
267    echo
268
269    # Step 2: Process configuration templates
270    process_templates
271    echo
272
273    # Step 3: Wait for dependencies
274    wait_for_dependencies
275    echo
276
277    # Step 4: Initialize application
278    initialize_app
279    echo
280
281    # Step 5: Start the application
282    log_info "Starting application..."
283
284    # Default command if none provided
285    if [[ $# -eq 0 ]]; then
286        set -- "/app/bin/server"
287    fi
288
289    log_info "Command: $*"
290    echo
291
292    # Execute the application
293    # Use exec to replace the shell with the application
294    # This ensures signals are properly forwarded
295    exec "$@" &
296    APP_PID=$!
297
298    # Wait for the application process
299    wait $APP_PID
300}
301
302# Run main with all arguments
303main "$@"