#!/bin/bash # Function to log messages with timestamp log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a /home/ubuntu/thrillwiki-deploy.log } # Function to check if a command exists command_exists() { command -v "$1" >/dev/null 2>&1 } # Function to wait for network connectivity wait_for_network() { log "Waiting for network connectivity..." local max_attempts=30 local attempt=1 while [ $attempt -le $max_attempts ]; do if curl -s --connect-timeout 5 https://github.com >/dev/null 2>&1; then log "Network connectivity confirmed" return 0 fi log "Network attempt $attempt/$max_attempts failed, retrying in 10 seconds..." sleep 10 attempt=$((attempt + 1)) done log "WARNING: Network connectivity check failed after $max_attempts attempts" return 1 } # Function to install uv if not available install_uv() { log "Checking for uv installation..." export PATH="/home/ubuntu/.cargo/bin:$PATH" if command_exists uv; then log "uv is already available" return 0 fi log "Installing uv..." # Wait for network connectivity first wait_for_network || { log "Network not available, skipping uv installation" return 1 } # Try to install uv with multiple attempts local max_attempts=3 local attempt=1 while [ $attempt -le $max_attempts ]; do log "uv installation attempt $attempt/$max_attempts" if curl -LsSf --connect-timeout 30 --retry 2 --retry-delay 5 https://astral.sh/uv/install.sh | sh; then # Reload PATH export PATH="/home/ubuntu/.cargo/bin:$PATH" if command_exists uv; then log "uv installed successfully" return 0 else log "uv installation completed but command not found, checking PATH..." # Try to source the shell profile to get updated PATH if [ -f /home/ubuntu/.bashrc ]; then source /home/ubuntu/.bashrc 2>/dev/null || true fi if [ -f /home/ubuntu/.cargo/env ]; then source /home/ubuntu/.cargo/env 2>/dev/null || true fi export PATH="/home/ubuntu/.cargo/bin:$PATH" if command_exists uv; then log "uv is now available after PATH update" return 0 fi fi fi log "uv installation attempt $attempt failed" attempt=$((attempt + 1)) [ $attempt -le $max_attempts ] && sleep 10 done log "Failed to install uv after $max_attempts attempts, will use pip fallback" return 1 } # Function to setup Python environment with fallbacks setup_python_env() { log "Setting up Python environment..." # Try to install uv first if not available install_uv export PATH="/home/ubuntu/.cargo/bin:$PATH" # Try uv first if command_exists uv; then log "Using uv for Python environment management" if uv venv .venv && source .venv/bin/activate; then if uv sync; then log "Successfully set up environment with uv" return 0 else log "uv sync failed, falling back to pip" fi else log "uv venv failed, falling back to pip" fi else log "uv not available, using pip" fi # Fallback to pip with venv log "Setting up environment with pip and venv" if python3 -m venv .venv && source .venv/bin/activate; then pip install --upgrade pip || log "WARNING: Failed to upgrade pip" # Try different dependency installation methods if [ -f pyproject.toml ]; then log "Installing dependencies from pyproject.toml" if pip install -e . || pip install .; then log "Successfully installed dependencies from pyproject.toml" return 0 else log "Failed to install from pyproject.toml" fi fi if [ -f requirements.txt ]; then log "Installing dependencies from requirements.txt" if pip install -r requirements.txt; then log "Successfully installed dependencies from requirements.txt" return 0 else log "Failed to install from requirements.txt" fi fi # Last resort: install common Django packages log "Installing basic Django packages as fallback" pip install django psycopg2-binary gunicorn || log "WARNING: Failed to install basic packages" else log "ERROR: Failed to create virtual environment" return 1 fi } # Function to setup database with fallbacks setup_database() { log "Setting up PostgreSQL database..." # Ensure PostgreSQL is running if ! sudo systemctl is-active --quiet postgresql; then log "Starting PostgreSQL service..." sudo systemctl start postgresql || { log "Failed to start PostgreSQL, trying alternative methods" sudo service postgresql start || { log "ERROR: Could not start PostgreSQL" return 1 } } fi # Create database user and database with error handling if sudo -u postgres createuser ubuntu 2>/dev/null || sudo -u postgres psql -c "SELECT 1 FROM pg_user WHERE usename = 'ubuntu'" | grep -q 1; then log "Database user 'ubuntu' created or already exists" else log "ERROR: Failed to create database user" return 1 fi if sudo -u postgres createdb thrillwiki_production 2>/dev/null || sudo -u postgres psql -lqt | cut -d \| -f 1 | grep -qw thrillwiki_production; then log "Database 'thrillwiki_production' created or already exists" else log "ERROR: Failed to create database" return 1 fi # Grant permissions sudo -u postgres psql -c "ALTER USER ubuntu WITH SUPERUSER;" || { log "WARNING: Failed to grant superuser privileges, trying alternative permissions" sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE thrillwiki_production TO ubuntu;" || log "WARNING: Failed to grant database privileges" } log "Database setup completed" } # Function to run Django commands with fallbacks run_django_commands() { log "Running Django management commands..." # Ensure we're in the virtual environment if [ ! -d ".venv" ] || ! source .venv/bin/activate; then log "WARNING: Virtual environment not found or failed to activate" # Try to run without venv activation fi # Function to run a Django command with fallbacks run_django_cmd() { local cmd="$1" local description="$2" log "Running: $description" # Try uv run first if command_exists uv && uv run manage.py $cmd; then log "Successfully ran '$cmd' with uv" return 0 fi # Try python in venv if python manage.py $cmd; then log "Successfully ran '$cmd' with python" return 0 fi # Try python3 if python3 manage.py $cmd; then log "Successfully ran '$cmd' with python3" return 0 fi log "WARNING: Failed to run '$cmd'" return 1 } # Run migrations run_django_cmd "migrate" "Database migrations" || log "WARNING: Database migration failed" # Collect static files run_django_cmd "collectstatic --noinput" "Static files collection" || log "WARNING: Static files collection failed" # Build Tailwind CSS (if available) if run_django_cmd "tailwind build" "Tailwind CSS build"; then log "Tailwind CSS built successfully" else log "Tailwind CSS build not available or failed - this is optional" fi } # Function to setup systemd services with fallbacks setup_services() { log "Setting up systemd services..." # Check if systemd service files exist if [ -f scripts/systemd/thrillwiki.service ]; then sudo cp scripts/systemd/thrillwiki.service /etc/systemd/system/ || { log "Failed to copy thrillwiki.service, creating basic service" create_basic_service } else log "Systemd service file not found, creating basic service" create_basic_service fi if [ -f scripts/systemd/thrillwiki-webhook.service ]; then sudo cp scripts/systemd/thrillwiki-webhook.service /etc/systemd/system/ || { log "Failed to copy webhook service, skipping" } else log "Webhook service file not found, skipping" fi # Update service files with correct paths if [ -f /etc/systemd/system/thrillwiki.service ]; then sudo sed -i "s|/opt/thrillwiki|/home/ubuntu/thrillwiki|g" /etc/systemd/system/thrillwiki.service sudo sed -i "s|User=thrillwiki|User=ubuntu|g" /etc/systemd/system/thrillwiki.service fi if [ -f /etc/systemd/system/thrillwiki-webhook.service ]; then sudo sed -i "s|/opt/thrillwiki|/home/ubuntu/thrillwiki|g" /etc/systemd/system/thrillwiki-webhook.service sudo sed -i "s|User=thrillwiki|User=ubuntu|g" /etc/systemd/system/thrillwiki-webhook.service fi # Reload systemd and start services sudo systemctl daemon-reload if sudo systemctl enable thrillwiki 2>/dev/null; then log "ThrillWiki service enabled" if sudo systemctl start thrillwiki; then log "ThrillWiki service started successfully" else log "WARNING: Failed to start ThrillWiki service" sudo systemctl status thrillwiki --no-pager || true fi else log "WARNING: Failed to enable ThrillWiki service" fi # Try to start webhook service if it exists if [ -f /etc/systemd/system/thrillwiki-webhook.service ]; then sudo systemctl enable thrillwiki-webhook 2>/dev/null && sudo systemctl start thrillwiki-webhook || { log "WARNING: Failed to start webhook service" } fi } # Function to create a basic systemd service if none exists create_basic_service() { log "Creating basic systemd service..." sudo tee /etc/systemd/system/thrillwiki.service > /dev/null << 'SERVICE_EOF' [Unit] Description=ThrillWiki Django Application After=network.target postgresql.service Wants=postgresql.service [Service] Type=exec User=ubuntu Group=ubuntu [AWS-SECRET-REMOVED] [AWS-SECRET-REMOVED]/.venv/bin:/home/ubuntu/.cargo/bin:/usr/local/bin:/usr/bin:/bin ExecStart=/home/ubuntu/thrillwiki/.venv/bin/python manage.py runserver 0.0.0.0:8000 Restart=always RestartSec=3 [Install] WantedBy=multi-user.target SERVICE_EOF log "Basic systemd service created" } # Function to setup web server (nginx) with fallbacks setup_webserver() { log "Setting up web server..." # Check if nginx is installed and running if command_exists nginx; then if ! sudo systemctl is-active --quiet nginx; then log "Starting nginx..." sudo systemctl start nginx || log "WARNING: Failed to start nginx" fi # Create basic nginx config if none exists if [ ! -f /etc/nginx/sites-available/thrillwiki ]; then log "Creating nginx configuration..." sudo tee /etc/nginx/sites-available/thrillwiki > /dev/null << 'NGINX_EOF' server { listen 80; server_name _; location /static/ { alias /home/ubuntu/thrillwiki/staticfiles/; } location /media/ { alias /home/ubuntu/thrillwiki/media/; } location / { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } } NGINX_EOF # Enable the site sudo ln -sf /etc/nginx/sites-available/thrillwiki /etc/nginx/sites-enabled/ || log "WARNING: Failed to enable nginx site" sudo nginx -t && sudo systemctl reload nginx || log "WARNING: nginx configuration test failed" fi else log "nginx not installed, ThrillWiki will run on port 8000 directly" fi } # Main deployment function main() { log "Starting ThrillWiki deployment..." # Wait for system to be ready log "Waiting for system to be ready..." sleep 30 # Wait for network wait_for_network || log "WARNING: Network check failed, continuing anyway" # Clone repository log "Cloning ThrillWiki repository..." export GITHUB_TOKEN=$(cat /home/ubuntu/.github-token 2>/dev/null || echo "") # Get the GitHub repository from environment or parameter GITHUB_REPO="${1:-}" if [ -z "$GITHUB_REPO" ]; then log "ERROR: GitHub repository not specified" return 1 fi if [ -d "/home/ubuntu/thrillwiki" ]; then log "ThrillWiki directory already exists, updating..." cd /home/ubuntu/thrillwiki git pull || log "WARNING: Failed to update repository" else if [ -n "$GITHUB_TOKEN" ]; then log "Cloning with GitHub token..." git clone https://$GITHUB_TOKEN@github.com/$GITHUB_REPO /home/ubuntu/thrillwiki || { log "Failed to clone with token, trying without..." git clone https://github.com/$GITHUB_REPO /home/ubuntu/thrillwiki || { log "ERROR: Failed to clone repository" return 1 } } else log "Cloning without GitHub token..." git clone https://github.com/$GITHUB_REPO /home/ubuntu/thrillwiki || { log "ERROR: Failed to clone repository" return 1 } fi cd /home/ubuntu/thrillwiki fi # Setup Python environment setup_python_env || { log "ERROR: Failed to set up Python environment" return 1 } # Setup environment file log "Setting up environment configuration..." if [ -f ***REMOVED***.example ]; then cp ***REMOVED***.example ***REMOVED*** || log "WARNING: Failed to copy ***REMOVED***.example" fi # Update ***REMOVED*** with production settings { echo "DEBUG=False" echo "DATABASE_URL=postgresql://ubuntu@localhost/thrillwiki_production" echo "ALLOWED_HOSTS=*" echo "STATIC_[AWS-SECRET-REMOVED]" } >> ***REMOVED*** # Setup database setup_database || { log "ERROR: Database setup failed" return 1 } # Run Django commands run_django_commands # Setup systemd services setup_services # Setup web server setup_webserver log "ThrillWiki deployment completed!" log "Application should be available at http://$(hostname -I | awk '{print $1}'):8000" log "Logs are available at /home/ubuntu/thrillwiki-deploy.log" } # Run main function and capture any errors main "$@" 2>&1 | tee -a /home/ubuntu/thrillwiki-deploy.log exit_code=${PIPESTATUS[0]} if [ $exit_code -eq 0 ]; then log "Deployment completed successfully!" else log "Deployment completed with errors (exit code: $exit_code)" fi exit $exit_code