566 lines
16 KiB
Groff
566 lines
16 KiB
Groff
# Dockerfile v0.0.2 - Enhanced Hexo Deployment Container
|
|
# Improvements:
|
|
# - Enhanced readability with heredoc syntax for scripts
|
|
# - Fixed PUID/PGID support for proper user/group mapping
|
|
# - Intelligent log rotation with 10MB file size limit
|
|
# - Optimized production image (removed vim, nodejs, npm)
|
|
# - Enhanced security configurations (CSP headers, MaxAuthTries, ClientAlive)
|
|
# - Dedicated health endpoint (/health)
|
|
# - Improved post-receive hook with detailed logging
|
|
# - Dynamic volume permission management
|
|
|
|
# ---- Stage 1: Builder/Base ----
|
|
# This stage sets up the base environment, installs build tools, creates scripts and templates
|
|
|
|
FROM ubuntu:22.04 AS builder
|
|
|
|
ARG TZ=Asia/Shanghai
|
|
ARG PUID=1000
|
|
ARG PGID=1000
|
|
|
|
ENV DEBIAN_FRONTEND=noninteractive
|
|
ENV TZ=${TZ}
|
|
|
|
# Set the timezone
|
|
RUN ln -sf /usr/share/zoneinfo/$TZ /etc/localtime && \
|
|
echo $TZ > /etc/timezone
|
|
|
|
# Install all required packages in a single layer
|
|
RUN apt-get update && \
|
|
apt-get install -y --no-install-recommends \
|
|
locales \
|
|
git \
|
|
nginx-full \
|
|
gettext-base && \
|
|
sed -i 's/# zh_CN.UTF-8 UTF-8/zh_CN.UTF-8 UTF-8/' /etc/locale.gen && \
|
|
locale-gen && \
|
|
update-locale LANG=zh_CN.UTF-8 && \
|
|
rm -rf /var/lib/apt/lists/*
|
|
|
|
# Create directories for artifacts
|
|
RUN mkdir -p /etc/container/templates && \
|
|
mkdir -p /app && \
|
|
mkdir -p /home/hexo
|
|
|
|
# Configure git hook with improved logging using heredoc
|
|
RUN git init --bare /home/hexo/hexo.git
|
|
|
|
# Create enhanced post-receive hook with detailed logging
|
|
RUN cat << 'EOF' > /home/hexo/hexo.git/hooks/post-receive
|
|
#!/bin/bash
|
|
|
|
# Enhanced post-receive hook with detailed logging
|
|
LOG_FILE="/var/log/container/deployment.log"
|
|
DEPLOY_TIME=$(date '+%Y-%m-%d %H:%M:%S')
|
|
|
|
log_deploy() {
|
|
echo "[$DEPLOY_TIME] $*" | tee -a "$LOG_FILE"
|
|
}
|
|
|
|
log_deploy "===== Starting Git Deployment ====="
|
|
log_deploy "Deploy initiated by: $(whoami)"
|
|
log_deploy "Git repository: $PWD"
|
|
log_deploy "Target directory: /home/www/hexo"
|
|
|
|
# Ensure target directory exists
|
|
if [ ! -d "/home/www/hexo" ]; then
|
|
log_deploy "Creating target directory /home/www/hexo"
|
|
mkdir -p /home/www/hexo
|
|
fi
|
|
|
|
# Checkout files
|
|
log_deploy "Checking out files..."
|
|
if git --work-tree=/home/www/hexo --git-dir=/home/hexo/hexo.git checkout -f; then
|
|
log_deploy "Git checkout completed successfully"
|
|
else
|
|
log_deploy "ERROR: Git checkout failed"
|
|
exit 1
|
|
fi
|
|
|
|
# Set proper ownership and permissions
|
|
log_deploy "Setting file ownership and permissions..."
|
|
if chown -R hexo:hexo /home/www/hexo; then
|
|
log_deploy "File ownership set successfully"
|
|
else
|
|
log_deploy "WARNING: Failed to set file ownership"
|
|
fi
|
|
|
|
if chmod -R 755 /home/www/hexo; then
|
|
log_deploy "File permissions set successfully"
|
|
else
|
|
log_deploy "WARNING: Failed to set file permissions"
|
|
fi
|
|
|
|
# Count deployed files
|
|
FILE_COUNT=$(find /home/www/hexo -type f | wc -l)
|
|
TOTAL_SIZE=$(du -sh /home/www/hexo 2>/dev/null | cut -f1)
|
|
|
|
log_deploy "Deployment completed successfully"
|
|
log_deploy "Files deployed: $FILE_COUNT"
|
|
log_deploy "Total size: $TOTAL_SIZE"
|
|
log_deploy "===== Git Deployment Finished ====="
|
|
EOF
|
|
|
|
RUN chmod +x /home/hexo/hexo.git/hooks/post-receive
|
|
|
|
# Backup original nginx.conf
|
|
RUN cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.bak
|
|
|
|
# Create enhanced SSH Config Template with security improvements using heredoc
|
|
RUN cat << 'EOF' > /etc/container/templates/sshd_config.template
|
|
# Enhanced SSH Configuration with Security Hardening
|
|
Port ${SSH_PORT:-22}
|
|
ListenAddress 0.0.0.0
|
|
ListenAddress ::
|
|
|
|
# Authentication settings
|
|
PermitRootLogin ${PERMIT_ROOT_LOGIN:-no}
|
|
PubkeyAuthentication yes
|
|
AuthorizedKeysFile .ssh/authorized_keys
|
|
PasswordAuthentication no
|
|
ChallengeResponseAuthentication no
|
|
UsePAM yes
|
|
|
|
# Security enhancements
|
|
MaxAuthTries 3
|
|
ClientAliveInterval 300
|
|
ClientAliveCountMax 2
|
|
LoginGraceTime 60
|
|
MaxStartups 10:30:60
|
|
|
|
# Disable unnecessary features
|
|
X11Forwarding no
|
|
AllowTcpForwarding no
|
|
GatewayPorts no
|
|
PermitTunnel no
|
|
PrintMotd no
|
|
|
|
# Environment and subsystem
|
|
AcceptEnv LANG LC_*
|
|
Subsystem sftp /usr/lib/openssh/sftp-server
|
|
|
|
# User restrictions
|
|
AllowUsers hexo
|
|
EOF
|
|
|
|
# Create enhanced Nginx Config Template with security headers and health endpoint using heredoc
|
|
RUN cat << 'EOF' > /etc/container/templates/nginx.conf.template
|
|
user ${NGINX_USER:-hexo};
|
|
worker_processes ${NGINX_WORKERS:-auto};
|
|
pid /var/run/nginx.pid;
|
|
|
|
events {
|
|
worker_connections ${NGINX_CONNECTIONS:-1024};
|
|
use epoll;
|
|
multi_accept on;
|
|
}
|
|
|
|
http {
|
|
include /etc/nginx/mime.types;
|
|
default_type application/octet-stream;
|
|
|
|
# Logging
|
|
access_log /var/log/nginx/access.log;
|
|
error_log /var/log/nginx/error.log;
|
|
|
|
# Performance optimizations
|
|
sendfile on;
|
|
tcp_nopush on;
|
|
tcp_nodelay on;
|
|
keepalive_timeout 65;
|
|
types_hash_max_size 2048;
|
|
|
|
# Enhanced security headers
|
|
add_header X-Frame-Options DENY always;
|
|
add_header X-Content-Type-Options nosniff always;
|
|
add_header X-XSS-Protection "1; mode=block" always;
|
|
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self';" always;
|
|
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
|
|
|
# Compression
|
|
gzip on;
|
|
gzip_vary on;
|
|
gzip_proxied any;
|
|
gzip_comp_level 6;
|
|
gzip_types
|
|
text/plain
|
|
text/css
|
|
text/xml
|
|
text/javascript
|
|
application/json
|
|
application/javascript
|
|
application/xml+rss
|
|
application/atom+xml
|
|
image/svg+xml;
|
|
|
|
server {
|
|
listen ${HTTP_PORT:-80};
|
|
server_name ${SERVER_NAME:-localhost};
|
|
root ${WEB_ROOT:-/home/www/hexo};
|
|
index index.html index.htm;
|
|
|
|
server_tokens off;
|
|
|
|
# Main site location
|
|
location / {
|
|
try_files $uri $uri/ =404;
|
|
}
|
|
|
|
# Dedicated health check endpoint
|
|
location /health {
|
|
access_log off;
|
|
return 200 "OK\n";
|
|
add_header Content-Type text/plain;
|
|
}
|
|
|
|
# Static assets with caching
|
|
location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff|woff2|ttf|eot|svg)$ {
|
|
expires 1y;
|
|
add_header Cache-Control "public, immutable";
|
|
add_header Vary Accept-Encoding;
|
|
}
|
|
|
|
# Security: deny access to hidden files
|
|
location ~ /\. {
|
|
deny all;
|
|
access_log off;
|
|
log_not_found off;
|
|
}
|
|
}
|
|
}
|
|
EOF
|
|
|
|
# Create enhanced start script with heredoc for better readability and maintainability
|
|
RUN cat << 'EOF' > /app/start.sh
|
|
#!/bin/bash
|
|
|
|
# Enhanced startup script with improved logging, error handling, and dynamic permissions
|
|
# Version: 0.0.2
|
|
|
|
# Color definitions for logging
|
|
RED="\033[0;31m"
|
|
GREEN="\033[0;32m"
|
|
YELLOW="\033[1;33m"
|
|
BLUE="\033[0;34m"
|
|
NC="\033[0m"
|
|
|
|
# Configuration
|
|
LOG_DIR="/var/log/container"
|
|
LOG_FILE="$LOG_DIR/services.log"
|
|
MAX_LOG_SIZE=10485760 # 10MB
|
|
|
|
# Logging functions
|
|
_log() {
|
|
local level_color=$1
|
|
local level_name=$2
|
|
shift 2
|
|
echo -e "${level_color}[${level_name}]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $*"
|
|
}
|
|
|
|
log_info() { _log "$BLUE" "INFO" "$@"; }
|
|
log_success() { _log "$GREEN" "SUCCESS" "$@"; }
|
|
log_warning() { _log "$YELLOW" "WARNING" "$@"; }
|
|
log_error() { _log "$RED" "ERROR" "$@"; }
|
|
|
|
# Setup logging with rotation
|
|
setup_logging() {
|
|
mkdir -p "$LOG_DIR"
|
|
touch "$LOG_FILE"
|
|
|
|
# Rotate log if it's too large
|
|
if [ -f "$LOG_FILE" ] && [ $(stat -c%s "$LOG_FILE" 2>/dev/null || echo 0) -gt $MAX_LOG_SIZE ]; then
|
|
log_info "Log file size exceeded ${MAX_LOG_SIZE} bytes, rotating..."
|
|
mv "$LOG_FILE" "${LOG_FILE}.old"
|
|
touch "$LOG_FILE"
|
|
log_info "Log rotation completed"
|
|
fi
|
|
|
|
log_info "Logging to console and $LOG_FILE"
|
|
exec > >(tee -a "$LOG_FILE") 2> >(tee -a "$LOG_FILE" >&2)
|
|
}
|
|
|
|
# Apply dynamic PUID/PGID if different from defaults
|
|
apply_dynamic_permissions() {
|
|
local current_uid=$(id -u hexo)
|
|
local current_gid=$(id -g hexo)
|
|
local target_uid=${PUID:-1000}
|
|
local target_gid=${PGID:-1000}
|
|
|
|
if [ "$current_uid" != "$target_uid" ] || [ "$current_gid" != "$target_gid" ]; then
|
|
log_info "Applying dynamic user/group mapping: $current_uid:$current_gid -> $target_uid:$target_gid"
|
|
|
|
# Update group if needed
|
|
if [ "$current_gid" != "$target_gid" ]; then
|
|
groupmod -g "$target_gid" hexo
|
|
log_info "Updated hexo group ID to $target_gid"
|
|
fi
|
|
|
|
# Update user if needed
|
|
if [ "$current_uid" != "$target_uid" ]; then
|
|
usermod -u "$target_uid" hexo
|
|
log_info "Updated hexo user ID to $target_uid"
|
|
fi
|
|
|
|
# Update ownership of important directories
|
|
log_info "Updating ownership of critical directories..."
|
|
chown -R hexo:hexo /home/hexo /home/www/hexo 2>/dev/null || true
|
|
log_success "Dynamic permissions applied successfully"
|
|
else
|
|
log_info "User/group IDs already match target values ($target_uid:$target_gid)"
|
|
fi
|
|
}
|
|
|
|
# Render configuration templates
|
|
render_config() {
|
|
log_info "Rendering configuration templates..."
|
|
local rendered=0
|
|
|
|
# Render SSH configuration
|
|
if envsubst < /etc/container/templates/sshd_config.template > /etc/ssh/sshd_config; then
|
|
log_success "SSH configuration rendered"
|
|
((rendered++))
|
|
else
|
|
log_error "Failed to render SSH configuration"
|
|
fi
|
|
|
|
# Render Nginx configuration
|
|
if envsubst < /etc/container/templates/nginx.conf.template > /etc/nginx/nginx.conf; then
|
|
log_success "Nginx configuration rendered"
|
|
((rendered++))
|
|
else
|
|
log_error "Failed to render Nginx configuration"
|
|
fi
|
|
|
|
if [ "$rendered" -eq 2 ]; then
|
|
log_success "All configuration files rendered successfully"
|
|
return 0
|
|
else
|
|
log_error "Failed to render some configuration files"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Start services
|
|
start_services() {
|
|
log_info "Starting services..."
|
|
|
|
# Generate SSH host keys if needed
|
|
if [ ! -f "/etc/ssh/ssh_host_rsa_key" ]; then
|
|
log_info "Generating SSH host keys..."
|
|
ssh-keygen -A
|
|
log_success "SSH host keys generated"
|
|
fi
|
|
|
|
# Start SSH service
|
|
log_info "Starting SSH service..."
|
|
/usr/sbin/sshd -D &
|
|
SSH_PID=$!
|
|
|
|
# Start Nginx service
|
|
log_info "Starting Nginx service..."
|
|
nginx -g "daemon off;" &
|
|
NGINX_PID=$!
|
|
|
|
# Wait for services to start
|
|
sleep 3
|
|
|
|
# Verify services are running
|
|
if kill -0 $SSH_PID 2>/dev/null && kill -0 $NGINX_PID 2>/dev/null; then
|
|
log_success "All services started successfully"
|
|
log_info "SSH PID: $SSH_PID"
|
|
log_info "Nginx PID: $NGINX_PID"
|
|
return 0
|
|
else
|
|
log_error "Failed to start some services"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Monitor services and restart if needed
|
|
monitor_services() {
|
|
log_info "Starting service monitoring (30s intervals)..."
|
|
|
|
while true; do
|
|
sleep 30
|
|
|
|
# Check SSH service
|
|
if ! kill -0 $SSH_PID 2>/dev/null; then
|
|
log_error "SSH service stopped unexpectedly, attempting restart..."
|
|
/usr/sbin/sshd -D &
|
|
SSH_PID=$!
|
|
if kill -0 $SSH_PID 2>/dev/null; then
|
|
log_success "SSH service restarted successfully (PID: $SSH_PID)"
|
|
else
|
|
log_error "Failed to restart SSH service"
|
|
fi
|
|
fi
|
|
|
|
# Check Nginx service
|
|
if ! kill -0 $NGINX_PID 2>/dev/null; then
|
|
log_error "Nginx service stopped unexpectedly, attempting restart..."
|
|
nginx -g "daemon off;" &
|
|
NGINX_PID=$!
|
|
if kill -0 $NGINX_PID 2>/dev/null; then
|
|
log_success "Nginx service restarted successfully (PID: $NGINX_PID)"
|
|
else
|
|
log_error "Failed to restart Nginx service"
|
|
fi
|
|
fi
|
|
done
|
|
}
|
|
|
|
# Graceful shutdown
|
|
cleanup() {
|
|
log_info "Received shutdown signal, gracefully stopping services..."
|
|
|
|
# Stop Nginx
|
|
if [ ! -z "$NGINX_PID" ] && kill -0 $NGINX_PID 2>/dev/null; then
|
|
log_info "Stopping Nginx (PID: $NGINX_PID)"
|
|
kill -TERM $NGINX_PID
|
|
wait $NGINX_PID 2>/dev/null
|
|
log_success "Nginx stopped gracefully"
|
|
fi
|
|
|
|
# Stop SSH
|
|
if [ ! -z "$SSH_PID" ] && kill -0 $SSH_PID 2>/dev/null; then
|
|
log_info "Stopping SSH (PID: $SSH_PID)"
|
|
kill -TERM $SSH_PID
|
|
wait $SSH_PID 2>/dev/null
|
|
log_success "SSH stopped gracefully"
|
|
fi
|
|
|
|
log_info "Container shutdown completed"
|
|
exit 0
|
|
}
|
|
|
|
# Main execution function
|
|
main() {
|
|
setup_logging
|
|
|
|
log_info "===== Hexo Container Starting (v0.0.2) ====="
|
|
log_info "Timestamp: $(date)"
|
|
log_info "Timezone: $TZ"
|
|
log_info "Current user: $(whoami)"
|
|
log_info "PUID: ${PUID:-1000}, PGID: ${PGID:-1000}"
|
|
|
|
# Apply dynamic permissions
|
|
apply_dynamic_permissions
|
|
|
|
# Render configurations
|
|
if ! render_config; then
|
|
log_error "Configuration rendering failed"
|
|
exit 1
|
|
fi
|
|
|
|
# Test configurations
|
|
log_info "Testing configurations..."
|
|
if ! /usr/sbin/sshd -t; then
|
|
log_error "SSH configuration test failed"
|
|
exit 1
|
|
fi
|
|
log_success "SSH configuration test passed"
|
|
|
|
if ! nginx -t; then
|
|
log_error "Nginx configuration test failed"
|
|
exit 1
|
|
fi
|
|
log_success "Nginx configuration test passed"
|
|
|
|
# Start services
|
|
if ! start_services; then
|
|
log_error "Service startup failed"
|
|
exit 1
|
|
fi
|
|
|
|
log_success "===== All services started successfully ====="
|
|
log_info "Container ready for connections"
|
|
log_info "SSH: Port ${SSH_PORT:-22}"
|
|
log_info "HTTP: Port ${HTTP_PORT:-80}"
|
|
log_info "Health check: http://localhost/health"
|
|
|
|
# Start monitoring
|
|
monitor_services
|
|
}
|
|
|
|
# Set up signal handlers
|
|
trap cleanup SIGTERM SIGINT
|
|
|
|
# Start main execution
|
|
main
|
|
EOF
|
|
|
|
RUN chmod +x /app/start.sh
|
|
|
|
# ---- Stage 2: Production ----
|
|
# This stage builds the final runtime image with only necessary dependencies
|
|
|
|
FROM ubuntu:22.04 AS production
|
|
|
|
ARG TZ=Asia/Shanghai
|
|
ARG PUID=1000
|
|
ARG PGID=1000
|
|
|
|
ENV DEBIAN_FRONTEND=noninteractive
|
|
ENV TZ=${TZ}
|
|
ENV PUID=${PUID}
|
|
ENV PGID=${PGID}
|
|
ENV LANG=zh_CN.UTF-8
|
|
|
|
# Copy timezone and locale settings from builder
|
|
COPY --from=builder /etc/localtime /etc/localtime
|
|
COPY --from=builder /etc/timezone /etc/timezone
|
|
COPY --from=builder /usr/lib/locale/zh_CN.utf8 /usr/lib/locale/zh_CN.utf8/
|
|
COPY --from=builder /etc/default/locale /etc/default/locale
|
|
|
|
# Install only runtime dependencies (removed vim, nodejs, npm for minimal production image)
|
|
RUN apt-get update && \
|
|
apt-get install -y --no-install-recommends \
|
|
openssh-server \
|
|
git \
|
|
nginx-full \
|
|
gettext-base \
|
|
curl \
|
|
ca-certificates && \
|
|
rm -rf /var/lib/apt/lists/*
|
|
|
|
# Create hexo user with proper PUID/PGID mapping
|
|
RUN groupadd -g ${PGID} hexo && \
|
|
useradd -u ${PUID} -g hexo -d /home/hexo -s /bin/bash hexo
|
|
|
|
# Create necessary directories with secure permissions
|
|
RUN mkdir -p /home/hexo/.ssh && \
|
|
mkdir -p /home/www/hexo && \
|
|
mkdir -p /home/www/ssl && \
|
|
mkdir -p /var/run/sshd && \
|
|
mkdir -p /var/log/container && \
|
|
mkdir -p /var/log/nginx
|
|
|
|
# Copy artifacts from builder stage
|
|
COPY --from=builder /home/hexo/hexo.git /home/hexo/hexo.git
|
|
COPY --from=builder /etc/container/templates /etc/container/templates/
|
|
COPY --from=builder /app/start.sh /root/start.sh
|
|
COPY --from=builder /etc/nginx/nginx.conf.bak /etc/nginx/nginx.conf.bak
|
|
|
|
# Set proper permissions for security
|
|
RUN chmod +x /root/start.sh && \
|
|
chmod +x /home/hexo/hexo.git/hooks/post-receive && \
|
|
chown -R hexo:hexo /home/www/hexo && \
|
|
chown -R hexo:hexo /home/hexo && \
|
|
chmod -R 755 /home/www/hexo && \
|
|
chmod 700 /home/hexo/.ssh && \
|
|
chmod 755 /var/log/container && \
|
|
chmod 755 /var/log/nginx
|
|
|
|
# Enhanced health check using the dedicated /health endpoint
|
|
HEALTHCHECK --interval=30s --timeout=10s --start-period=15s --retries=3 \
|
|
CMD curl -f http://localhost/health || exit 1
|
|
|
|
# Define volumes for persistent data
|
|
VOLUME ["/home/www/hexo", "/home/hexo/.ssh", "/home/www/ssl", "/home/hexo/hexo.git", "/var/log/container", "/var/log/nginx"]
|
|
|
|
# Expose ports
|
|
EXPOSE 22 80 443
|
|
|
|
# Set the startup command
|
|
CMD ["/root/start.sh"]
|