#!/bin/bash # Docker Hexo Static Blog v0.0.3-fixed - Container Start Script # This script handles container initialization and service startup # Fixed: Git Hook permission issues for deployment logging set -e # Environment variables with defaults PUID=${PUID:-1000} PGID=${PGID:-1000} TZ=${TZ:-Asia/Shanghai} # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # Logging function log() { echo -e "${GREEN}[$(date '+%Y-%m-%d %H:%M:%S')] $1${NC}" } log_warn() { echo -e "${YELLOW}[$(date '+%Y-%m-%d %H:%M:%S')] WARNING: $1${NC}" } log_error() { echo -e "${RED}[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: $1${NC}" } # Set timezone if [ ! -z "$TZ" ]; then log "Setting timezone to $TZ" ln -snf /usr/share/zoneinfo/$TZ /etc/localtime echo $TZ > /etc/timezone fi # Update hexo user UID/GID if needed current_uid=$(id -u hexo) current_gid=$(id -g hexo) if [ "$current_uid" != "$PUID" ] || [ "$current_gid" != "$PGID" ]; then log "Updating hexo user UID/GID from $current_uid:$current_gid to $PUID:$PGID" # Update group first groupmod -g "$PGID" hexo # Update user usermod -u "$PUID" -g "$PGID" hexo # Fix ownership of hexo directories chown -R hexo:hexo /home/hexo chown -R hexo:hexo /home/www/hexo fi # Ensure proper permissions log "Setting up permissions..." chown -R hexo:hexo /home/hexo chown -R hexo:hexo /home/www/hexo chown hexo:hexo /var/log/container chmod 755 /var/log/container chmod +x /home/hexo/hexo.git/hooks/post-receive # Setup SSH host keys if they don't exist log "Checking SSH host keys..." if [ ! -f /etc/ssh/ssh_host_rsa_key ]; then log "Generating SSH host keys..." ssh-keygen -A fi # Setup SSH configuration from template log "Setting up SSH configuration..." if [ -f /etc/container/templates/sshd_config.template ]; then envsubst < /etc/container/templates/sshd_config.template > /etc/ssh/sshd_config else log_warn "SSH template not found, using default configuration" fi # Setup SSH authorized keys for hexo user (for testing) log "Setting up SSH access for hexo user..." mkdir -p /home/hexo/.ssh chmod 700 /home/hexo/.ssh # Create a test SSH key pair if it doesn't exist (for development/testing) if [ ! -f /home/hexo/.ssh/authorized_keys ]; then log "Creating test SSH key for hexo user..." # Generate a temporary key pair for testing ssh-keygen -t rsa -b 2048 -f /home/hexo/.ssh/id_rsa -N "" -C "hexo@container" # Add the public key to authorized_keys cat /home/hexo/.ssh/id_rsa.pub > /home/hexo/.ssh/authorized_keys # Set proper permissions chmod 600 /home/hexo/.ssh/authorized_keys chmod 600 /home/hexo/.ssh/id_rsa chmod 644 /home/hexo/.ssh/id_rsa.pub log "Test SSH key created and configured" log "Private key: /home/hexo/.ssh/id_rsa" log "Public key: /home/hexo/.ssh/id_rsa.pub" fi # Ensure SSH directory has correct ownership chown -R hexo:hexo /home/hexo/.ssh # Setup nginx configuration from template log "Setting up nginx configuration..." if [ -f /etc/container/templates/nginx.conf.template ]; then envsubst < /etc/container/templates/nginx.conf.template > /etc/nginx/nginx.conf else log_warn "Nginx template not found, using default configuration" fi # Test nginx configuration log "Testing nginx configuration..." nginx -t || { log_error "Nginx configuration test failed" exit 1 } # Create necessary directories log "Creating necessary directories..." mkdir -p /var/run/sshd mkdir -p /var/log/nginx mkdir -p /var/log/container # Setup deployment log with proper permissions and file locking support log "Setting up deployment log..." DEPLOYMENT_LOG="/var/log/container/deployment.log" DEPLOYMENT_LOG_LOCK="/var/log/container/deployment.log.lock" # Function to safely write to deployment log (thread-safe) safe_log_write() { local message="$1" local timestamp=$(date '+%Y-%m-%d %H:%M:%S') local full_message="[$timestamp] $message" # Use flock for file locking to prevent race conditions ( flock -w 5 200 || { echo "Failed to acquire lock for deployment.log" >&2 return 1 } echo "$full_message" >> "$DEPLOYMENT_LOG" ) 200>"$DEPLOYMENT_LOG_LOCK" } # Check if deployment.log already exists if [ -f "$DEPLOYMENT_LOG" ]; then log "Deployment log already exists, preserving existing file" # Only fix permissions if file exists chown hexo:hexo "$DEPLOYMENT_LOG" chmod 664 "$DEPLOYMENT_LOG" log "Fixed permissions for existing deployment.log" # Add a restart separator to distinguish new session from old logs safe_log_write "========== Container Restart $(date) ==========" else # Only create if it doesn't exist log "Creating new deployment.log file" touch "$DEPLOYMENT_LOG" chown hexo:hexo "$DEPLOYMENT_LOG" chmod 664 "$DEPLOYMENT_LOG" safe_log_write "Container startup - deployment.log initialized" log "New deployment.log created with correct ownership" fi # Ensure lock file has correct permissions (critical fix for Git Hook access) log "Setting up deployment log lock file permissions..." if [ -f "$DEPLOYMENT_LOG_LOCK" ]; then log "Lock file exists (pre-created in Dockerfile), verifying permissions" # Verify and fix permissions if needed current_owner=$(stat -c '%U:%G' "$DEPLOYMENT_LOG_LOCK" 2>/dev/null || echo "unknown:unknown") if [ "$current_owner" != "hexo:hexo" ]; then log "Fixing lock file ownership from $current_owner to hexo:hexo" chown hexo:hexo "$DEPLOYMENT_LOG_LOCK" fi chmod 664 "$DEPLOYMENT_LOG_LOCK" log "Lock file permissions verified and corrected if needed" else log "Lock file not found, creating with correct permissions (fallback)" touch "$DEPLOYMENT_LOG_LOCK" chown hexo:hexo "$DEPLOYMENT_LOG_LOCK" chmod 664 "$DEPLOYMENT_LOG_LOCK" log "Lock file created as fallback" fi log "Deployment log setup completed" # Enhanced log rotation with conflict resolution setup_log_rotation() { log "Starting log rotation service..." while true; do sleep 1800 # 30 minutes log "Running scheduled log rotation check..." # Use flock to prevent conflicts with log monitoring ( flock -w 10 201 || { log_warn "Could not acquire rotation lock, skipping this cycle" continue } # Run logrotate if the log file exists and is large enough if [ -f "$DEPLOYMENT_LOG" ]; then LOG_SIZE=$(stat -c%s "$DEPLOYMENT_LOG" 2>/dev/null || echo 0) if [ "$LOG_SIZE" -gt 20480 ]; then # 20KB log "Log file size ($LOG_SIZE bytes) exceeds 20KB, running logrotate..." # Create backup before rotation BACKUP_FILE="${DEPLOYMENT_LOG}.$(date +%Y%m%d_%H%M%S)" cp "$DEPLOYMENT_LOG" "$BACKUP_FILE" # Truncate original file instead of removing it (safer for tail -f) > "$DEPLOYMENT_LOG" chown hexo:hexo "$DEPLOYMENT_LOG" chmod 664 "$DEPLOYMENT_LOG" # Compress backup gzip "$BACKUP_FILE" 2>/dev/null || log_warn "Failed to compress backup" # Keep only last 5 backups ls -t "${DEPLOYMENT_LOG}".*.gz 2>/dev/null | tail -n +6 | xargs rm -f 2>/dev/null || true safe_log_write "Log rotation completed - file size was $LOG_SIZE bytes" log "Deployment log rotation completed" else log "Log file size ($LOG_SIZE bytes) is under 20KB threshold, skipping rotation" fi else log "No deployment.log file found, skipping rotation" fi ) 201>"${DEPLOYMENT_LOG_LOCK}.rotation" # Check nginx logs for logfile in /var/log/nginx/access.log /var/log/nginx/error.log; do if [ -f "$logfile" ] && [ $(stat -c%s "$logfile" 2>/dev/null || echo 0) -gt 52428800 ]; then log "Rotating $(basename $logfile) (>50MB)" mv "$logfile" "$logfile.$(date +%Y%m%d_%H%M%S)" touch "$logfile" chown hexo:hexo "$logfile" chmod 664 "$logfile" # Reload nginx to reopen log files nginx -s reload # Keep only last 3 rotated logs ls -t "$logfile".* 2>/dev/null | tail -n +4 | xargs rm -f 2>/dev/null || true fi done done } # Enhanced deployment log monitoring with error recovery and restart-safe mode setup_deployment_monitor() { log "Setting up deployment log monitoring..." local monitor_pid_file="/var/run/deployment_monitor.pid" local position_file="/var/run/deployment_monitor.pos" # Check if deployment log exists and get current size for restart-safe monitoring if [ -f "$DEPLOYMENT_LOG" ]; then local current_size=$(stat -c%s "$DEPLOYMENT_LOG" 2>/dev/null || echo 0) log "Deployment log exists ($current_size bytes), will monitor only NEW entries to avoid duplicate output" # Store current position to skip existing content on container restart echo "$current_size" > "$position_file" else # New deployment log, start monitoring from beginning echo "0" > "$position_file" log "New deployment log, will monitor all entries" fi while true; do # Use tail with --bytes to skip existing content on restart if [ -f "$position_file" ]; then local start_pos=$(cat "$position_file" 2>/dev/null || echo 0) if [ "$start_pos" -gt 0 ]; then # Skip existing content by using tail with --bytes=+N to start from position N tail -F --bytes=+$((start_pos + 1)) "$DEPLOYMENT_LOG" 2>/dev/null | while IFS= read -r line; do # Add prefix to distinguish deployment logs in container output echo "[DEPLOY] $line" done & else # Start from beginning for new files tail -F "$DEPLOYMENT_LOG" 2>/dev/null | while IFS= read -r line; do # Add prefix to distinguish deployment logs in container output echo "[DEPLOY] $line" done & fi else # Fallback to normal tail if position file doesn't exist tail -F "$DEPLOYMENT_LOG" 2>/dev/null | while IFS= read -r line; do # Add prefix to distinguish deployment logs in container output echo "[DEPLOY] $line" done & fi local tail_pid=$! echo $tail_pid > "$monitor_pid_file" log "Deployment log monitor started (PID: $tail_pid) - monitoring from position $(cat "$position_file" 2>/dev/null || echo 0)" # Wait for the tail process wait $tail_pid # If tail exits, log the error and restart after a delay local exit_code=$? log_warn "Deployment log monitor exited (code: $exit_code), restarting in 5 seconds..." sleep 5 done } # Cleanup function for graceful shutdown cleanup() { log "Received shutdown signal, cleaning up..." # Kill deployment monitor if running if [ -f /var/run/deployment_monitor.pid ]; then local monitor_pid=$(cat /var/run/deployment_monitor.pid 2>/dev/null) if [ -n "$monitor_pid" ] && kill -0 "$monitor_pid" 2>/dev/null; then log "Stopping deployment monitor (PID: $monitor_pid)" kill "$monitor_pid" 2>/dev/null || true fi rm -f /var/run/deployment_monitor.pid rm -f /var/run/deployment_monitor.pos # Clean up position file fi # Stop services gracefully log "Stopping nginx..." nginx -s quit 2>/dev/null || true log "Stopping SSH daemon..." pkill sshd 2>/dev/null || true # Final log entry safe_log_write "Container shutdown completed" exit 0 } # Set up signal handlers for graceful shutdown trap cleanup SIGTERM SIGINT # Start log rotation in background setup_log_rotation & LOG_ROTATION_PID=$! # Start SSH daemon log "Starting SSH daemon..." /usr/sbin/sshd -D & SSHD_PID=$! # Start nginx log "Starting nginx..." nginx -g "daemon off;" & NGINX_PID=$! # Create a simple health check endpoint cat > /home/www/hexo/health </dev/null; then log_error "Nginx died, restarting..." safe_log_write "Nginx service died, restarting" nginx -g "daemon off;" & NGINX_PID=$! log "Nginx restarted (new PID: $NGINX_PID)" fi # Check if sshd is still running if ! kill -0 "$SSHD_PID" 2>/dev/null; then log_error "SSH daemon died, restarting..." safe_log_write "SSH daemon died, restarting" /usr/sbin/sshd -D & SSHD_PID=$! log "SSH daemon restarted (new PID: $SSHD_PID)" fi # Check if log rotation is still running if ! kill -0 "$LOG_ROTATION_PID" 2>/dev/null; then log_error "Log rotation died, restarting..." safe_log_write "Log rotation service died, restarting" setup_log_rotation & LOG_ROTATION_PID=$! log "Log rotation restarted (new PID: $LOG_ROTATION_PID)" fi # Check if deployment monitor is still running if ! kill -0 "$MONITOR_PID" 2>/dev/null; then log_error "Deployment monitor died, restarting..." safe_log_write "Deployment monitor died, restarting" setup_deployment_monitor & MONITOR_PID=$! log "Deployment monitor restarted (new PID: $MONITOR_PID)" fi sleep 30 done } # Start service monitoring monitor_services