#!/bin/bash # ThrillWiki VM Deployment Script # This script runs on the Linux VM to deploy the latest code and restart the server set -e # Exit on any error # Configuration PROJECT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" LOG_DIR="$PROJECT_DIR/logs" BACKUP_DIR="$PROJECT_DIR/backups" DEPLOY_LOG="$LOG_DIR/deploy.log" SERVICE_NAME="thrillwiki" # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color # Logging function log() { local message="[$(date +'%Y-%m-%d %H:%M:%S')] $1" echo -e "${BLUE}${message}${NC}" echo "$message" >> "$DEPLOY_LOG" } log_success() { local message="[$(date +'%Y-%m-%d %H:%M:%S')] ✓ $1" echo -e "${GREEN}${message}${NC}" echo "$message" >> "$DEPLOY_LOG" } log_warning() { local message="[$(date +'%Y-%m-%d %H:%M:%S')] ⚠ $1" echo -e "${YELLOW}${message}${NC}" echo "$message" >> "$DEPLOY_LOG" } log_error() { local message="[$(date +'%Y-%m-%d %H:%M:%S')] ✗ $1" echo -e "${RED}${message}${NC}" echo "$message" >> "$DEPLOY_LOG" } # Create necessary directories create_directories() { log "Creating necessary directories..." mkdir -p "$LOG_DIR" "$BACKUP_DIR" log_success "Directories created" } # Backup current deployment backup_current() { log "Creating backup of current deployment..." local timestamp=$(date +'%Y%m%d_%H%M%S') local backup_path="$BACKUP_DIR/backup_$timestamp" # Create backup of current code if [ -d "$PROJECT_DIR/.git" ]; then local current_commit=$(git -C "$PROJECT_DIR" rev-parse HEAD) echo "$current_commit" > "$backup_path.commit" log_success "Backup created with commit: ${current_commit:0:8}" else log_warning "Not a git repository, skipping backup" fi } # Stop the service stop_service() { log "Stopping ThrillWiki service..." # Stop systemd service if it exists if systemctl is-active --quiet "$SERVICE_NAME" 2>/dev/null; then sudo systemctl stop "$SERVICE_NAME" log_success "Systemd service stopped" else log "Systemd service not running" fi # Kill any remaining Django processes on port 8000 if lsof -ti :8000 >/dev/null 2>&1; then log "Stopping processes on port 8000..." lsof -ti :8000 | xargs kill -9 2>/dev/null || true log_success "Port 8000 processes stopped" fi # Clean up Python cache log "Cleaning Python cache..." find "$PROJECT_DIR" -type d -name "__pycache__" -exec rm -r {} + 2>/dev/null || true log_success "Python cache cleaned" } # Update code from git update_code() { log "Updating code from git repository..." cd "$PROJECT_DIR" # Fetch latest changes git fetch origin log "Fetched latest changes" # Get current and new commit info local old_commit=$(git rev-parse HEAD) local new_commit=$(git rev-parse origin/main) if [ "$old_commit" = "$new_commit" ]; then log_warning "No new commits to deploy" return 0 fi log "Updating from ${old_commit:0:8} to ${new_commit:0:8}" # Pull latest changes git reset --hard origin/main log_success "Code updated successfully" # Show what changed log "Changes in this deployment:" git log --oneline "$old_commit..$new_commit" || true } # Install/update dependencies update_dependencies() { log "Updating dependencies..." cd "$PROJECT_DIR" # Check if UV is installed if ! command -v uv &> /dev/null; then log_error "UV is not installed. Installing UV..." curl -LsSf https://astral.sh/uv/install.sh | sh source $HOME/.cargo/env fi # Sync dependencies uv sync --no-dev || { log_error "Failed to sync dependencies" return 1 } log_success "Dependencies updated" } # Run database migrations run_migrations() { log "Running database migrations..." cd "$PROJECT_DIR" # Check for pending migrations if uv run manage.py showmigrations --plan | grep -q "\[ \]"; then log "Applying database migrations..." uv run manage.py migrate || { log_error "Database migrations failed" return 1 } log_success "Database migrations completed" else log "No pending migrations" fi } # Collect static files collect_static() { log "Collecting static files..." cd "$PROJECT_DIR" uv run manage.py collectstatic --noinput || { log_warning "Static file collection failed, continuing..." } log_success "Static files collected" } # Start the service start_service() { log "Starting ThrillWiki service..." cd "$PROJECT_DIR" # Start systemd service if it exists if systemctl list-unit-files | grep -q "^$SERVICE_NAME.service"; then sudo systemctl start "$SERVICE_NAME" sudo systemctl enable "$SERVICE_NAME" # Wait for service to start sleep 5 if systemctl is-active --quiet "$SERVICE_NAME"; then log_success "Systemd service started successfully" else log_error "Systemd service failed to start" return 1 fi else log_warning "Systemd service not found, starting manually..." # Start server in background nohup ./scripts/ci-start.sh > "$LOG_DIR/server.log" 2>&1 & local server_pid=$! # Wait for server to start sleep 5 if kill -0 $server_pid 2>/dev/null; then echo $server_pid > "$LOG_DIR/server.pid" log_success "Server started manually with PID: $server_pid" else log_error "Failed to start server manually" return 1 fi fi } # Health check health_check() { log "Performing health check..." local max_attempts=30 local attempt=1 while [ $attempt -le $max_attempts ]; do if curl -f -s http://localhost:8000/health >/dev/null 2>&1; then log_success "Health check passed" return 0 fi log "Health check attempt $attempt/$max_attempts failed, retrying..." sleep 2 ((attempt++)) done log_error "Health check failed after $max_attempts attempts" return 1 } # Cleanup old backups cleanup_backups() { log "Cleaning up old backups..." # Keep only the last 10 backups cd "$BACKUP_DIR" ls -t backup_*.commit 2>/dev/null | tail -n +11 | xargs rm -f 2>/dev/null || true log_success "Old backups cleaned up" } # Rollback function rollback() { log_error "Deployment failed, attempting rollback..." local latest_backup=$(ls -t "$BACKUP_DIR"/backup_*.commit 2>/dev/null | head -n 1) if [ -n "$latest_backup" ]; then local backup_commit=$(cat "$latest_backup") log "Rolling back to commit: ${backup_commit:0:8}" cd "$PROJECT_DIR" git reset --hard "$backup_commit" # Restart service stop_service start_service if health_check; then log_success "Rollback completed successfully" else log_error "Rollback failed - manual intervention required" fi else log_error "No backup found for rollback" fi } # Main deployment function deploy() { log "=== ThrillWiki Deployment Started ===" log "Timestamp: $(date)" log "User: $(whoami)" log "Host: $(hostname)" # Trap errors for rollback trap rollback ERR create_directories backup_current stop_service update_code update_dependencies run_migrations collect_static start_service health_check cleanup_backups # Remove error trap trap - ERR log_success "=== Deployment Completed Successfully ===" log "Server is now running the latest code" log "Check logs at: $LOG_DIR/" } # Script execution case "${1:-deploy}" in deploy) deploy ;; stop) stop_service ;; start) start_service ;; restart) stop_service start_service health_check ;; status) if systemctl is-active --quiet "$SERVICE_NAME" 2>/dev/null; then echo "Service is running" elif [ -f "$LOG_DIR/server.pid" ] && kill -0 "$(cat "$LOG_DIR/server.pid")" 2>/dev/null; then echo "Server is running manually" else echo "Service is not running" fi ;; health) health_check ;; *) echo "Usage: $0 {deploy|stop|start|restart|status|health}" exit 1 ;; esac