#!/bin/bash # SmoothSchedule Production Deployment Script # Usage: ./deploy.sh [server_user@server_host] [services...] # Example: ./deploy.sh poduck@smoothschedule.com # Build all # Example: ./deploy.sh poduck@smoothschedule.com traefik # Build only traefik # Example: ./deploy.sh poduck@smoothschedule.com django nginx # Build django and nginx # # Available services: django, traefik, nginx, postgres, celeryworker, celerybeat, flower, awscli # Use --no-migrate to skip migrations (useful for config-only changes like traefik) # # This script deploys from git repository, not local files. # Changes must be committed and pushed before deploying. set -e # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' # No Color # Parse arguments SERVER="" SERVICES="" SKIP_MIGRATE=false for arg in "$@"; do if [[ "$arg" == "--no-migrate" ]]; then SKIP_MIGRATE=true elif [[ -z "$SERVER" ]]; then SERVER="$arg" else SERVICES="$SERVICES $arg" fi done SERVER=${SERVER:-"poduck@smoothschedule.com"} SERVICES=$(echo "$SERVICES" | xargs) # Trim whitespace REPO_URL="https://git.talova.net/poduck/smoothschedule.git" REMOTE_DIR="/home/poduck/smoothschedule" echo -e "${GREEN}===================================" echo "SmoothSchedule Deployment" echo "===================================${NC}" echo "Target server: $SERVER" if [[ -n "$SERVICES" ]]; then echo "Services to rebuild: $SERVICES" else echo "Services to rebuild: ALL" fi if [[ "$SKIP_MIGRATE" == "true" ]]; then echo "Migrations: SKIPPED" fi echo "" # Function to print status print_status() { echo -e "${GREEN}>>> $1${NC}" } print_warning() { echo -e "${YELLOW}>>> $1${NC}" } print_error() { echo -e "${RED}>>> $1${NC}" } # Step 1: Check for uncommitted changes print_status "Step 1: Checking for uncommitted changes..." if [[ -n $(git status --porcelain) ]]; then print_error "You have uncommitted changes. Please commit and push before deploying." git status --short exit 1 fi # Check if local is ahead of remote LOCAL_COMMIT=$(git rev-parse HEAD) REMOTE_COMMIT=$(git rev-parse @{u} 2>/dev/null || echo "") if [[ -z "$REMOTE_COMMIT" ]]; then print_error "No upstream branch configured. Please push your changes first." exit 1 fi if [[ "$LOCAL_COMMIT" != "$REMOTE_COMMIT" ]]; then print_warning "Local branch differs from remote. Checking if ahead..." AHEAD=$(git rev-list --count @{u}..HEAD) if [[ "$AHEAD" -gt 0 ]]; then print_error "You have $AHEAD unpushed commit(s). Please push before deploying." exit 1 fi fi print_status "All changes committed and pushed!" # Step 2: Deploy on server print_status "Step 2: Deploying on server..." ssh "$SERVER" "bash -s" << ENDSSH set -e echo ">>> Setting up project directory..." # Backup .envs if they exist (secrets not in git) if [ -d "$REMOTE_DIR/smoothschedule/.envs" ]; then echo ">>> Backing up .envs secrets..." cp -r "$REMOTE_DIR/smoothschedule/.envs" /tmp/.envs-backup elif [ -d "$REMOTE_DIR/.envs" ]; then # Old structure - .envs was at root level echo ">>> Backing up .envs secrets (old location)..." cp -r "$REMOTE_DIR/.envs" /tmp/.envs-backup fi # Backup .ssh if it exists (SSH keys not in git) if [ -d "$REMOTE_DIR/smoothschedule/.ssh" ]; then echo ">>> Backing up .ssh keys..." cp -r "$REMOTE_DIR/smoothschedule/.ssh" /tmp/.ssh-backup elif [ -d "$REMOTE_DIR/.ssh" ]; then # Old structure echo ">>> Backing up .ssh keys (old location)..." cp -r "$REMOTE_DIR/.ssh" /tmp/.ssh-backup fi if [ ! -d "$REMOTE_DIR/.git" ]; then echo ">>> Cloning repository for the first time..." # Remove old non-git deployment if exists if [ -d "$REMOTE_DIR" ]; then rm -rf "$REMOTE_DIR" fi git clone "$REPO_URL" "$REMOTE_DIR" else echo ">>> Repository exists, pulling latest changes..." cd "$REMOTE_DIR" git fetch origin git reset --hard origin/main fi cd "$REMOTE_DIR" # Restore .envs secrets if [ -d /tmp/.envs-backup ] && [ "$(ls -A /tmp/.envs-backup 2>/dev/null)" ]; then echo ">>> Restoring .envs secrets..." mkdir -p "$REMOTE_DIR/smoothschedule/.envs" cp -r /tmp/.envs-backup/* "$REMOTE_DIR/smoothschedule/.envs/" rm -rf /tmp/.envs-backup fi # Restore .ssh keys if [ -d /tmp/.ssh-backup ] && [ "$(ls -A /tmp/.ssh-backup 2>/dev/null)" ]; then echo ">>> Restoring .ssh keys..." mkdir -p "$REMOTE_DIR/smoothschedule/.ssh" cp -r /tmp/.ssh-backup/* "$REMOTE_DIR/smoothschedule/.ssh/" rm -rf /tmp/.ssh-backup fi echo ">>> Current commit:" git log -1 --oneline cd smoothschedule # Build images (all or specific services) if [[ -n "$SERVICES" ]]; then echo ">>> Building Docker images: $SERVICES..." docker compose -f docker-compose.production.yml build $SERVICES else echo ">>> Building all Docker images..." docker compose -f docker-compose.production.yml build fi echo ">>> Starting containers..." docker compose -f docker-compose.production.yml up -d echo ">>> Waiting for containers to start..." sleep 5 # Run migrations unless skipped if [[ "$SKIP_MIGRATE" != "true" ]]; then echo ">>> Running database migrations..." docker compose -f docker-compose.production.yml exec -T django sh -c 'export DATABASE_URL=postgres://\${POSTGRES_USER}:\${POSTGRES_PASSWORD}@\${POSTGRES_HOST}:\${POSTGRES_PORT}/\${POSTGRES_DB} && python manage.py migrate' echo ">>> Collecting static files..." docker compose -f docker-compose.production.yml exec -T django sh -c 'export DATABASE_URL=postgres://\${POSTGRES_USER}:\${POSTGRES_PASSWORD}@\${POSTGRES_HOST}:\${POSTGRES_PORT}/\${POSTGRES_DB} && python manage.py collectstatic --noinput' echo ">>> Seeding/updating platform plugins for all tenants..." docker compose -f docker-compose.production.yml exec -T django sh -c 'export DATABASE_URL=postgres://\${POSTGRES_USER}:\${POSTGRES_PASSWORD}@\${POSTGRES_HOST}:\${POSTGRES_PORT}/\${POSTGRES_DB} && python -c " from django_tenants.utils import get_tenant_model from django.core.management import call_command Tenant = get_tenant_model() for tenant in Tenant.objects.exclude(schema_name=\"public\"): print(f\" Seeding plugins for {tenant.schema_name}...\") call_command(\"tenant_command\", \"seed_platform_plugins\", schema=tenant.schema_name, verbosity=0) print(\" Done!\") "' else echo ">>> Skipping migrations (--no-migrate flag used)" fi echo ">>> Deployment complete!" ENDSSH echo "" print_status "===================================" print_status "Deployment Complete!" print_status "===================================" echo "" echo "Your application should now be running at:" echo " - https://smoothschedule.com" echo " - https://platform.smoothschedule.com" echo " - https://*.smoothschedule.com (tenant subdomains)" echo "" echo "To view logs:" echo " ssh $SERVER 'cd ~/smoothschedule/smoothschedule && docker compose -f docker-compose.production.yml logs -f'" echo "" echo "To check status:" echo " ssh $SERVER 'cd ~/smoothschedule/smoothschedule && docker compose -f docker-compose.production.yml ps'"