#!/bin/bash # Deploy mdBook documentation to custom server # Usage: deploy-docs.sh [environment] # Environments: staging, production, custom set -euo pipefail # ============================================================================ # Configuration # ============================================================================ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" DOCS_DIR="$REPO_ROOT/docs" BUILD_DIR="$DOCS_DIR/book" TIMESTAMP=$(date +%Y%m%d_%H%M%S) LOG_FILE="/tmp/docs-deploy-$TIMESTAMP.log" # Default environment ENVIRONMENT="${1:-production}" # ============================================================================ # Logging # ============================================================================ log() { echo "[$(date +'%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE" } error() { echo "[ERROR] $*" | tee -a "$LOG_FILE" >&2 exit 1 } success() { echo "[✓] $*" | tee -a "$LOG_FILE" } # ============================================================================ # Environment Configuration # ============================================================================ load_config() { local env=$1 local config_file="$SCRIPT_DIR/.deploy-config.$env" if [ ! -f "$config_file" ]; then error "Configuration file not found: $config_file" fi log "Loading configuration for environment: $env" source "$config_file" # Validate required variables local required_vars=("DEPLOY_HOST" "DEPLOY_USER" "DEPLOY_PATH" "DEPLOY_METHOD") for var in "${required_vars[@]}"; do if [ -z "${!var:-}" ]; then error "Missing required configuration: $var" fi done success "Configuration loaded" } # ============================================================================ # Pre-Flight Checks # ============================================================================ preflight_checks() { log "Running preflight checks..." # Check if mdBook is built if [ ! -d "$BUILD_DIR" ]; then error "Build directory not found: $BUILD_DIR" fi if [ ! -f "$BUILD_DIR/index.html" ]; then error "Built documentation not found: $BUILD_DIR/index.html" fi # Verify essential files local required_files=("index.html" "print.html" "css/general.css" "js/book.js") for file in "${required_files[@]}"; do if [ ! -f "$BUILD_DIR/$file" ]; then error "Missing essential file: $file" fi done # Check connectivity case $DEPLOY_METHOD in ssh|sftp) if ! ssh -o ConnectTimeout=5 "$DEPLOY_USER@$DEPLOY_HOST" "echo ok" > /dev/null 2>&1; then error "Cannot connect to $DEPLOY_USER@$DEPLOY_HOST via SSH" fi ;; http) if ! curl -sf "$DEPLOY_ENDPOINT/health" > /dev/null 2>&1; then log "⚠ Deployment endpoint health check failed (non-blocking)" fi ;; esac success "Preflight checks passed" } # ============================================================================ # Deployment Methods # ============================================================================ deploy_ssh() { log "Deploying via SSH to $DEPLOY_USER@$DEPLOY_HOST:$DEPLOY_PATH" # Create backup on remote ssh "$DEPLOY_USER@$DEPLOY_HOST" \ "mkdir -p $DEPLOY_PATH/backups && \ [ -d $DEPLOY_PATH/current ] && \ mv $DEPLOY_PATH/current $DEPLOY_PATH/backups/backup_$TIMESTAMP || true" # Upload files rsync -avz \ --delete \ --exclude '.gitignore' \ --exclude 'CNAME' \ "$BUILD_DIR/" \ "$DEPLOY_USER@$DEPLOY_HOST:$DEPLOY_PATH/current/" # Update symlink (atomic) ssh "$DEPLOY_USER@$DEPLOY_HOST" \ "ln -sfT $DEPLOY_PATH/current $DEPLOY_PATH/docs && \ chmod -R 755 $DEPLOY_PATH/current" success "SSH deployment completed" } deploy_sftp() { log "Deploying via SFTP to $DEPLOY_USER@$DEPLOY_HOST:$DEPLOY_PATH" # Create SFTP batch commands local sftp_batch=$(mktemp) cat > "$sftp_batch" << SFTP_CMDS cd $DEPLOY_PATH -mkdir backups -mkdir current mput -r $BUILD_DIR/* current/ quit SFTP_CMDS # Execute SFTP batch sftp -b "$sftp_batch" "$DEPLOY_USER@$DEPLOY_HOST" rm "$sftp_batch" success "SFTP deployment completed" } deploy_http() { log "Deploying via HTTP to $DEPLOY_ENDPOINT" # Create temporary tarball local tar_file=$(mktemp --suffix=.tar.gz) tar -czf "$tar_file" -C "$BUILD_DIR" . # Upload via HTTP local response=$(curl -s -w "\n%{http_code}" \ -X POST \ -H "Authorization: Bearer $DEPLOY_TOKEN" \ -F "archive=@$tar_file" \ "$DEPLOY_ENDPOINT/deploy") local http_code=$(echo "$response" | tail -n1) local body=$(echo "$response" | head -n-1) rm "$tar_file" if [ "$http_code" != "200" ] && [ "$http_code" != "201" ]; then error "HTTP deployment failed (HTTP $http_code): $body" fi success "HTTP deployment completed (HTTP $http_code)" } deploy_docker() { log "Deploying via Docker to $DEPLOY_REGISTRY" local image="$DEPLOY_REGISTRY/vapora-docs:$ENVIRONMENT-$TIMESTAMP" local dockerfile=$(mktemp) # Generate Dockerfile cat > "$dockerfile" << 'EOF' FROM nginx:alpine COPY . /usr/share/nginx/html EXPOSE 80 CMD ["nginx", "-g", "daemon off;"] EOF # Build image log "Building Docker image: $image" docker build -f "$dockerfile" -t "$image" "$BUILD_DIR" docker tag "$image" "$DEPLOY_REGISTRY/vapora-docs:$ENVIRONMENT-latest" # Push image log "Pushing image to registry" docker push "$image" docker push "$DEPLOY_REGISTRY/vapora-docs:$ENVIRONMENT-latest" rm "$dockerfile" log "Container image available: $image" success "Docker deployment completed" } deploy_s3() { log "Deploying to S3: s3://$AWS_BUCKET/$DEPLOY_PATH" aws s3 sync \ "$BUILD_DIR/" \ "s3://$AWS_BUCKET/$DEPLOY_PATH/" \ --delete \ --region "${AWS_REGION:-us-east-1}" \ --cache-control "public, max-age=300" # Invalidate CloudFront if configured if [ -n "${CLOUDFRONT_DISTRIBUTION:-}" ]; then log "Invalidating CloudFront distribution" aws cloudfront create-invalidation \ --distribution-id "$CLOUDFRONT_DISTRIBUTION" \ --paths "/$DEPLOY_PATH/*" \ --region "${AWS_REGION:-us-east-1}" fi success "S3 deployment completed" } deploy_gcs() { log "Deploying to Google Cloud Storage: gs://$GCS_BUCKET/$DEPLOY_PATH" gsutil -m rsync -r -d "$BUILD_DIR/" "gs://$GCS_BUCKET/$DEPLOY_PATH/" # Set cache control gsutil -m setmeta -h "Cache-Control:public, max-age=300" \ "gs://$GCS_BUCKET/$DEPLOY_PATH/**" success "GCS deployment completed" } # ============================================================================ # Post-Deployment Verification # ============================================================================ verify_deployment() { log "Verifying deployment..." case $DEPLOY_METHOD in ssh|sftp) # Check if files exist on remote ssh "$DEPLOY_USER@$DEPLOY_HOST" \ "[ -f $DEPLOY_PATH/docs/index.html ] && echo 'Files verified' || exit 1" ;; http) # Hit deployment endpoint if ! curl -sf "$DEPLOY_ENDPOINT/health" > /dev/null; then log "⚠ Health check failed (deployment may still be in progress)" fi ;; docker) log "Docker image deployed: $image" ;; s3) # Verify S3 object aws s3 ls "s3://$AWS_BUCKET/$DEPLOY_PATH/index.html" \ --region "${AWS_REGION:-us-east-1}" > /dev/null ;; gcs) # Verify GCS object gsutil stat "gs://$GCS_BUCKET/$DEPLOY_PATH/index.html" > /dev/null ;; esac success "Deployment verified" } # ============================================================================ # Rollback # ============================================================================ rollback() { log "Rolling back deployment..." case $DEPLOY_METHOD in ssh) if [ -d "$DEPLOY_PATH/backups/backup_$TIMESTAMP" ]; then ssh "$DEPLOY_USER@$DEPLOY_HOST" \ "ln -sfT $DEPLOY_PATH/backups/backup_$TIMESTAMP $DEPLOY_PATH/docs" success "Rollback completed" else error "No backup available for rollback" fi ;; *) error "Rollback not implemented for deployment method: $DEPLOY_METHOD" ;; esac } # ============================================================================ # Main # ============================================================================ main() { log "Starting documentation deployment" log "Environment: $ENVIRONMENT" log "Repository: $REPO_ROOT" log "Build directory: $BUILD_DIR" log "Log file: $LOG_FILE" # Load configuration load_config "$ENVIRONMENT" # Preflight checks preflight_checks # Deploy based on method case $DEPLOY_METHOD in ssh) deploy_ssh ;; sftp) deploy_sftp ;; http) deploy_http ;; docker) deploy_docker ;; s3) deploy_s3 ;; gcs) deploy_gcs ;; *) error "Unknown deployment method: $DEPLOY_METHOD" ;; esac # Verify deployment verify_deployment success "Documentation deployment completed successfully" log "Summary:" log " Environment: $ENVIRONMENT" log " Method: $DEPLOY_METHOD" log " Build size: $(du -sh "$BUILD_DIR" | cut -f1)" log " Timestamp: $TIMESTAMP" log " Log: $LOG_FILE" } # Handle errors trap 'error "Deployment failed at line $LINENO"' ERR # Run main function main "$@"