- Add comprehensive timeBlocks translations (ES, FR, DE, EN) - Add myAvailability translations (ES, FR, DE, EN) - Add full helpTimeBlocks guide content (ES, FR, DE, EN) - Add contracts guide translations (ES) - Fix DATABASE_URL env var in deploy.sh for seed_platform_plugins - Update Contracts page and HelpContracts guide 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
217 lines
7.0 KiB
Bash
Executable File
217 lines
7.0 KiB
Bash
Executable File
#!/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'"
|