Files
office_translator/deploy.sh
Sepehr Ramezani 2f7347b4db
Some checks failed
Build and Deploy / Backend Tests (push) Has been cancelled
Build and Deploy / Frontend Build Check (push) Has been cancelled
Build and Deploy / Build Docker Images (push) Has been cancelled
Build and Deploy / Deploy to Server (push) Has been cancelled
feat: fix registration 500, add forgot-password flow, frontend validation
- Fix MissingGreenlet: sync_engine now uses psycopg2 instead of asyncpg
- Fix bcrypt/passlib compat: pin bcrypt<4.1 in requirements
- Fix legacy password_hash NOT NULL: alter column to nullable in migration
- Add frontend password validation (uppercase + lowercase + digit)
- Add forgot-password and reset-password backend endpoints
- Add forgot-password and reset-password frontend pages
- Add email_service.py (SMTP via admin settings)
- Add reset_token/reset_token_expires columns to User model
- Migrate legacy JSON-only users to DB on password reset request
- Mount data/ volume in docker-compose.local.yml for persistence
- Add production deployment config (Dockerfile, nginx, deploy.sh)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-01 16:23:51 +02:00

467 lines
13 KiB
Bash
Executable File

#!/bin/bash
# ============================================================
# Office Translator - Deployment Script
# ============================================================
# Usage: ./deploy.sh <command> [options]
#
# Commands:
# start Build and start all services
# stop Stop all services
# restart Restart all services
# status Show services status
# logs Show logs (follow mode)
# build Build/rebuild images
# health Run health checks
# backup Backup PostgreSQL database
# clean Remove all containers, volumes, and images
# shell Open a shell in the backend container
# migrate Run database migrations
# help Show this help message
#
# Options:
# --env <file> Use a specific env file (default: .env.docker)
# --prod Use production compose file (docker-compose.yml)
# --no-build Skip build step on start
# --rebuild Force rebuild without cache
#
# Examples:
# ./deploy.sh start # Start with defaults (local)
# ./deploy.sh start --rebuild # Force rebuild
# ./deploy.sh start --prod # Start production stack
# ./deploy.sh logs backend # Show backend logs
# ./deploy.sh logs # Show all logs
# ./deploy.sh backup # Backup database
# ./deploy.sh clean # Full cleanup
# ============================================================
set -euo pipefail
# ---- Colors ----
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
BOLD='\033[1m'
NC='\033[0m'
# ---- Configuration ----
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$SCRIPT_DIR"
COMPOSE_LOCAL="docker-compose.local.yml"
COMPOSE_PROD="docker-compose.yml"
ENV_FILE=".env.docker"
COMPOSE_FILE="$COMPOSE_LOCAL"
COMMAND=""
SERVICE=""
NO_BUILD=false
REBUILD=false
# ---- Helper Functions ----
info() { echo -e "${BLUE}[INFO]${NC} $*"; }
success() { echo -e "${GREEN}[OK]${NC} $*"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
error() { echo -e "${RED}[ERROR]${NC} $*" >&2; }
header() { echo ""; echo -e "${BOLD}${CYAN}========================================${NC}"; echo -e "${BOLD}${CYAN} $*${NC}"; echo -e "${BOLD}${CYAN}========================================${NC}"; }
# Run docker compose with the right file and env
dc() {
docker compose -f "$COMPOSE_FILE" --env-file "$ENV_FILE" "$@"
}
# ---- Parse Arguments ----
COMMAND="${1:-help}"
shift 2>/dev/null || true
while [[ $# -gt 0 ]]; do
case $1 in
--env)
ENV_FILE="$2"
shift 2
;;
--prod)
COMPOSE_FILE="$COMPOSE_PROD"
shift
;;
--no-build)
NO_BUILD=true
shift
;;
--rebuild)
REBUILD=true
shift
;;
backend|frontend|postgres|redis|nginx|ollama)
SERVICE="$1"
shift
;;
*)
shift
;;
esac
done
# ---- Prerequisite Checks ----
check_prerequisites() {
if ! command -v docker &> /dev/null; then
error "Docker is not installed. Install it from https://docs.docker.com/get-docker/"
exit 1
fi
if ! docker info &> /dev/null 2>&1; then
error "Docker daemon is not running. Start Docker Desktop or the Docker service."
exit 1
fi
if ! docker compose version &> /dev/null 2>&1; then
error "Docker Compose V2 is not available. Update Docker or install docker-compose."
exit 1
fi
if [ ! -f "$ENV_FILE" ]; then
error "Environment file '$ENV_FILE' not found."
if [ "$ENV_FILE" == ".env.docker" ]; then
info "Creating .env.docker from default settings..."
cat > .env.docker << 'ENVEOF'
ENV=production
LOG_LEVEL=INFO
LOG_FORMAT=console
ENABLE_REQUEST_LOGGING=true
TRANSLATION_SERVICE=google
GOOGLE_TRANSLATE_ENABLED=true
DEEPL_ENABLED=false
OPENAI_ENABLED=false
OLLAMA_ENABLED=false
OPENROUTER_ENABLED=false
PROVIDER_FALLBACK_CHAIN=google,deepl,openai,ollama,openrouter
FALLBACK_CHAIN_CLASSIC=google,deepl
FALLBACK_CHAIN_LLM=ollama,openai
MAX_FILE_SIZE_MB=50
MAX_REQUEST_SIZE_MB=100
REQUEST_TIMEOUT_SECONDS=300
RATE_LIMIT_ENABLED=true
RATE_LIMIT_PER_MINUTE=30
RATE_LIMIT_PER_HOUR=200
TRANSLATIONS_PER_MINUTE=10
TRANSLATIONS_PER_HOUR=50
MAX_CONCURRENT_TRANSLATIONS=5
CLEANUP_ENABLED=true
CLEANUP_INTERVAL_MINUTES=5
FILE_TTL_MINUTES=60
INPUT_FILE_TTL_MINUTES=30
OUTPUT_FILE_TTL_MINUTES=120
DISK_WARNING_THRESHOLD_GB=5.0
DISK_CRITICAL_THRESHOLD_GB=1.0
ENABLE_HSTS=false
CORS_ORIGINS=http://localhost,http://localhost:3000,http://localhost:8000
MAX_MEMORY_PERCENT=80
POSTGRES_USER=translate
POSTGRES_PASSWORD=translate_local_2026
POSTGRES_DB=translate_db
POSTGRES_HOST=postgres
POSTGRES_PORT=5432
DATABASE_ECHO=false
ADMIN_USERNAME=admin
ADMIN_PASSWORD=admin123
ADMIN_TOKEN_SECRET=docker_local_admin_token_secret_2026_abc123xyz
JWT_SECRET_KEY=docker_local_jwt_secret_key_2026_x7k9m3p5q8r2s4t6
FRONTEND_URL=http://localhost
NEXT_PUBLIC_API_URL=
BACKEND_URL=http://backend:8000
HTTP_PORT=80
ENVEOF
success "Created .env.docker with default local settings"
else
exit 1
fi
fi
# Check for port conflicts
if command -v lsof &> /dev/null; then
for PORT in 80 3000 5432 6379; do
if lsof -i ":$PORT" -sTCP:LISTEN &> /dev/null 2>&1; then
warn "Port $PORT is already in use. This may cause conflicts."
fi
done
fi
success "Prerequisites OK (Docker + Compose + $ENV_FILE)"
}
# ---- Command Functions ----
cmd_start() {
header "Office Translator - Starting"
check_prerequisites
if [ "$REBUILD" = true ]; then
info "Building images (no cache)..."
dc build --no-cache ${SERVICE:-}
elif [ "$NO_BUILD" = false ]; then
info "Building images..."
dc build ${SERVICE:-}
else
info "Skipping build (--no-build)"
fi
info "Starting services..."
dc up -d ${SERVICE:-}
info "Waiting for services to be ready..."
local max_wait=120
local elapsed=0
while [ $elapsed -lt $max_wait ]; do
local backend_ok=false
local frontend_ok=false
if curl -sf http://localhost:8000/health > /dev/null 2>&1; then
backend_ok=true
fi
if curl -sf http://localhost:3000 > /dev/null 2>&1; then
frontend_ok=true
fi
if [ "$backend_ok" = true ] && [ "$frontend_ok" = true ]; then
break
fi
sleep 3
elapsed=$((elapsed + 3))
printf "\r Waiting... (%ds/%ds) backend=%s frontend=%s" \
"$elapsed" "$max_wait" "$backend_ok" "$frontend_ok"
done
echo ""
# Health check
echo ""
cmd_health
# Show status
echo ""
dc ps --format "table {{.Name}}\t{{.Status}}\t{{.Ports}}"
echo ""
success "Application is running!"
echo ""
info "Access points:"
echo " Frontend: http://localhost"
echo " Backend: http://localhost:8000"
echo " API docs: http://localhost:8000/docs"
echo " Admin: http://localhost/admin (admin / admin123)"
echo " Health: http://localhost/health"
echo ""
info "Useful commands:"
echo " ./deploy.sh logs Follow logs"
echo " ./deploy.sh status Check status"
echo " ./deploy.sh stop Stop all"
}
cmd_stop() {
header "Stopping Services"
dc down --remove-orphans ${SERVICE:-}
success "Services stopped"
}
cmd_restart() {
header "Restarting Services"
info "Stopping..."
dc restart ${SERVICE:-}
success "Services restarted"
sleep 5
cmd_health
}
cmd_status() {
header "Service Status"
dc ps --format "table {{.Name}}\t{{.Status}}\t{{.Ports}}"
echo ""
# Quick health
info "Health checks:"
local backend_code=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:8000/health 2>/dev/null || echo "000")
local frontend_code=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:3000 2>/dev/null || echo "000")
local nginx_code=$(curl -s -o /dev/null -w "%{http_code}" http://localhost/health 2>/dev/null || echo "000")
[ "$backend_code" = "200" ] && success "Backend (HTTP $backend_code)" || error "Backend (HTTP $backend_code)"
[ "$frontend_code" = "200" ] && success "Frontend (HTTP $frontend_code)" || error "Frontend (HTTP $frontend_code)"
[ "$nginx_code" = "200" ] && success "Nginx (HTTP $nginx_code)" || error "Nginx (HTTP $nginx_code)"
}
cmd_logs() {
local target="${SERVICE:-}"
if [ -n "$target" ]; then
dc logs -f --tail 100 "$target"
else
dc logs -f --tail 50
fi
}
cmd_build() {
header "Building Images"
check_prerequisites
if [ "$REBUILD" = true ]; then
info "Building without cache..."
dc build --no-cache ${SERVICE:-}
else
info "Building..."
dc build ${SERVICE:-}
fi
success "Build complete"
}
cmd_health() {
info "Running health checks..."
# Backend
local backend_response=$(curl -sf http://localhost:8000/health 2>/dev/null || echo '{"status":"unreachable"}')
if echo "$backend_response" | python3 -c "import sys,json; d=json.load(sys.stdin); exit(0 if d.get('status')=='healthy' else 1)" 2>/dev/null; then
success "Backend: healthy"
echo "$backend_response" | python3 -m json.tool 2>/dev/null | sed 's/^/ /'
else
local backend_code=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:8000/health 2>/dev/null || echo "000")
error "Backend: unhealthy (HTTP $backend_code)"
fi
# Frontend
if curl -sf http://localhost:3000 > /dev/null 2>&1; then
success "Frontend: responding"
else
error "Frontend: not responding"
fi
# Nginx
if curl -sf http://localhost/health > /dev/null 2>&1; then
success "Nginx: proxying correctly"
else
warn "Nginx: not reachable on port 80"
fi
# PostgreSQL
if dc exec -T postgres pg_isready -U translate -d translate_db &> /dev/null; then
success "PostgreSQL: ready"
else
error "PostgreSQL: not ready"
fi
# Redis
if dc exec -T redis redis-cli ping &> /dev/null; then
success "Redis: ready (PONG)"
else
error "Redis: not ready"
fi
}
cmd_backup() {
header "Database Backup"
local timestamp=$(date +%Y%m%d_%H%M%S)
local backup_dir="backups"
mkdir -p "$backup_dir"
local backup_file="$backup_dir/translate_db_${timestamp}.sql.gz"
info "Backing up PostgreSQL to $backup_file..."
dc exec -T postgres pg_dump -U translate translate_db | gzip > "$backup_file"
local size=$(du -h "$backup_file" | cut -f1)
success "Backup complete: $backup_file ($size)"
}
cmd_clean() {
header "Full Cleanup"
warn "This will remove all containers, volumes, and images for this project."
read -p "Are you sure? [y/N] " -n 1 -r
echo ""
if [[ $REPLY =~ ^[Yy]$ ]]; then
info "Stopping and removing containers..."
dc down -v --rmi local --remove-orphans
info "Pruning dangling resources..."
docker system prune -f
success "Cleanup complete"
else
info "Cancelled"
fi
}
cmd_shell() {
local target="${SERVICE:-backend}"
info "Opening shell in $target container..."
dc exec "$target" /bin/bash || dc exec "$target" /bin/sh
}
cmd_migrate() {
header "Running Database Migrations"
info "Running alembic upgrade head..."
dc exec backend alembic upgrade head
success "Migrations complete"
}
cmd_help() {
echo ""
echo -e "${BOLD}Office Translator - Deployment Script${NC}"
echo ""
echo "Usage: ./deploy.sh <command> [options]"
echo ""
echo -e "${BOLD}Commands:${NC}"
echo " start Build and start all services"
echo " stop Stop all services"
echo " restart Restart all services"
echo " status Show services status"
echo " logs [svc] Show logs (optional: backend, frontend, postgres, redis, nginx)"
echo " build Build/rebuild Docker images"
echo " health Run health checks on all services"
echo " backup Backup PostgreSQL database"
echo " clean Remove all containers, volumes, and images"
echo " shell [svc] Open shell in a container (default: backend)"
echo " migrate Run database migrations"
echo " help Show this help message"
echo ""
echo -e "${BOLD}Options:${NC}"
echo " --env <file> Use a specific env file (default: .env.docker)"
echo " --prod Use production compose file"
echo " --no-build Skip build step"
echo " --rebuild Force rebuild without Docker cache"
echo ""
echo -e "${BOLD}Examples:${NC}"
echo " ./deploy.sh start"
echo " ./deploy.sh start --rebuild"
echo " ./deploy.sh start --prod --env .env.production"
echo " ./deploy.sh logs backend"
echo " ./deploy.sh shell postgres"
echo " ./deploy.sh backup"
echo ""
}
# ---- Main ----
case "$COMMAND" in
start) cmd_start ;;
stop) cmd_stop ;;
restart) cmd_restart ;;
status) cmd_status ;;
logs) cmd_logs ;;
build) cmd_build ;;
health) cmd_health ;;
backup) cmd_backup ;;
clean) cmd_clean ;;
shell) cmd_shell ;;
migrate) cmd_migrate ;;
help|--help|-h)
cmd_help
;;
*)
error "Unknown command: $COMMAND"
cmd_help
exit 1
;;
esac