diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md index d995db9..d9b31a9 100644 --- a/DEPLOYMENT.md +++ b/DEPLOYMENT.md @@ -215,7 +215,7 @@ events { http { upstream backend { - server backend:8000; + server backend:8048; } upstream frontend { @@ -405,7 +405,7 @@ docker-compose -f docker-compose.prod.yml exec backend python -c "from app.datab docker-compose -f docker-compose.prod.yml logs frontend # Check if backend is accessible -curl http://backend:8000/health +curl http://backend:8048/health ``` ### SSL certificate issues diff --git a/QUICKSTART.md b/QUICKSTART.md index 0951aea..8fb6872 100644 --- a/QUICKSTART.md +++ b/QUICKSTART.md @@ -41,7 +41,7 @@ 4. Configure: - Name: "APAC Ops Bot" - Supported account types: "Accounts in this organizational directory only" - - Redirect URI: `http://localhost:8000/api/v1/auth/msal/callback` + - Redirect URI: `http://localhost:8048/api/v1/auth/msal/callback` 5. After registration, note down: - **Application (client) ID** - **Directory (tenant) ID** @@ -81,9 +81,9 @@ docker-compose logs postgres docker-compose up --build backend ``` -The backend will be available at: **http://localhost:8000** -- API Docs: http://localhost:8000/docs -- Health Check: http://localhost:8000/health +The backend will be available at: **http://localhost:8048** +- API Docs: http://localhost:8048/docs +- Health Check: http://localhost:8048/health ### Step 4: Run Database Migrations @@ -115,7 +115,7 @@ The frontend will be available at: **http://localhost:3000** ```bash # Health check -curl http://localhost:8000/health +curl http://localhost:8048/health # Expected response: # {"status":"healthy","app":"Seapac Ops Bot","environment":"development"} @@ -184,7 +184,7 @@ apac-ops-bot/ **Solution:** ```bash # Check what's using the port -lsof -i :8000 # or :5432, :6379 +lsof -i :8048 # or :5432, :6379 # Stop existing containers docker-compose down diff --git a/README.md b/README.md index cdb7c84..16c3be1 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ cp .env.example .env **Required Frontend environment variables:** - `REACT_APP_AZURE_CLIENT_ID` - Same as backend Azure client ID - `REACT_APP_AZURE_TENANT_ID` - Same as backend Azure tenant ID -- `REACT_APP_API_URL` - Backend API URL (default: http://localhost:8000/api/v1) +- `REACT_APP_API_URL` - Backend API URL (default: /apac-ops-bot-back/api/v1) ### 3. Start services with Docker Compose @@ -80,7 +80,7 @@ docker-compose up --build This will start: - PostgreSQL (port 5432) - Redis (port 6379) -- Backend API (port 8000) +- Backend API (port 8048) - Frontend (port 3000) **First run:** The backend will automatically create database tables on startup. @@ -88,8 +88,8 @@ This will start: ### 4. Access the application - **Frontend Application:** http://localhost:3000 -- **API Documentation:** http://localhost:8000/docs -- **Health Check:** http://localhost:8000/health +- **API Documentation:** http://localhost:8048/docs +- **Health Check:** http://localhost:8048/health ## Development @@ -185,8 +185,8 @@ apac-ops-bot/ ## API Documentation Once the backend is running, access the interactive API documentation at: -- Swagger UI: http://localhost:8000/docs -- ReDoc: http://localhost:8000/redoc +- Swagger UI: http://localhost:8048/docs +- ReDoc: http://localhost:8048/redoc ## Security diff --git a/backend/.env.example b/backend/.env.example index 3dbdc5a..d98f1f6 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -12,7 +12,7 @@ DATABASE_URL=postgresql+asyncpg://apac_ops_bot:password@localhost:5432/apac_ops_ AZURE_TENANT_ID=your-tenant-id AZURE_CLIENT_ID=your-client-id AZURE_CLIENT_SECRET=your-client-secret -AZURE_REDIRECT_URI=http://localhost:8000/api/v1/auth/msal/callback +AZURE_REDIRECT_URI=http://localhost:8048/api/v1/auth/msal/callback # OpenAI Responses API OPENAI_API_KEY=your-openai-api-key-here diff --git a/backend/Dockerfile b/backend/Dockerfile index e036703..efcd85a 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -30,10 +30,10 @@ RUN pip install --no-cache-dir -r requirements-dev.txt COPY . . # Expose port -EXPOSE 8000 +EXPOSE 8048 # Development command (with hot reload) -CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"] +CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8048", "--reload"] # Stage 3: Production environment FROM base as production @@ -47,7 +47,7 @@ RUN useradd -m -u 1000 appuser && \ USER appuser # Expose port -EXPOSE 8000 +EXPOSE 8048 # Production command with multiple workers -CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4"] +CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8048", "--workers", "4"] diff --git a/backend/app/main.py b/backend/app/main.py index 11365b2..af858a2 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -78,6 +78,6 @@ if __name__ == "__main__": uvicorn.run( "app.main:app", host="0.0.0.0", - port=8000, + port=8048, reload=settings.DEBUG, ) diff --git a/backend/tests/test_config.py b/backend/tests/test_config.py index c01857d..f929492 100644 --- a/backend/tests/test_config.py +++ b/backend/tests/test_config.py @@ -78,13 +78,13 @@ def test_settings_azure_ad_configuration(): AZURE_TENANT_ID="test-tenant-id", AZURE_CLIENT_ID="test-client-id", AZURE_CLIENT_SECRET="test-client-secret", - AZURE_REDIRECT_URI="http://localhost:8000/api/v1/auth/callback", + AZURE_REDIRECT_URI="http://localhost:8048/api/v1/auth/callback", ) assert settings.AZURE_TENANT_ID == "test-tenant-id" assert settings.AZURE_CLIENT_ID == "test-client-id" assert settings.AZURE_CLIENT_SECRET == "test-client-secret" - assert settings.AZURE_REDIRECT_URI == "http://localhost:8000/api/v1/auth/callback" + assert settings.AZURE_REDIRECT_URI == "http://localhost:8048/api/v1/auth/callback" def test_settings_redis_url(): diff --git a/deploy.sh b/deploy.sh new file mode 100755 index 0000000..c50ad93 --- /dev/null +++ b/deploy.sh @@ -0,0 +1,244 @@ +#!/bin/bash +# +# APAC Ops Bot Deployment Script +# Idempotent deployment for Ubuntu server at /opt/apac-ops-bot/ +# +# Usage: sudo ./deploy.sh +# +# Note: Run 'git pull origin main' manually before running this script +# (root user typically doesn't have SSH keys for git) + +set -e + +# 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 functions +log_info() { + echo -e "[$(date '+%Y-%m-%d %H:%M:%S')] ${BLUE}[INFO]${NC} $1" +} + +log_success() { + echo -e "[$(date '+%Y-%m-%d %H:%M:%S')] ${GREEN}[SUCCESS]${NC} $1" +} + +log_warn() { + echo -e "[$(date '+%Y-%m-%d %H:%M:%S')] ${YELLOW}[WARN]${NC} $1" +} + +log_error() { + echo -e "[$(date '+%Y-%m-%d %H:%M:%S')] ${RED}[ERROR]${NC} $1" +} + +# Error handler +error_exit() { + log_error "$1" + exit 1 +} + +# Configuration +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +FRONTEND_DEPLOY_PATH="/var/www/html/apac-ops-bot" +BACKEND_PORT=8048 +HEALTH_CHECK_RETRIES=30 +HEALTH_CHECK_INTERVAL=2 + +log_info "Starting APAC Ops Bot deployment..." +log_info "Working directory: $SCRIPT_DIR" + +# ----------------------------------------------------------------------------- +# Pre-flight checks +# ----------------------------------------------------------------------------- +log_info "Running pre-flight checks..." + +# Check if running as root +if [[ $EUID -ne 0 ]]; then + error_exit "This script must be run as root (use sudo)" +fi + +# Check Docker +if ! command -v docker &> /dev/null; then + error_exit "Docker is not installed" +fi +log_info "Docker found: $(docker --version)" + +# Check docker-compose (try both v1 and v2 syntax) +if command -v docker-compose &> /dev/null; then + DOCKER_COMPOSE="docker-compose" +elif docker compose version &> /dev/null; then + DOCKER_COMPOSE="docker compose" +else + error_exit "docker-compose is not installed" +fi +log_info "Docker Compose found: $($DOCKER_COMPOSE version --short 2>/dev/null || $DOCKER_COMPOSE version)" + +# Check Node.js +if ! command -v node &> /dev/null; then + error_exit "Node.js is not installed" +fi +NODE_VERSION=$(node --version) +log_info "Node.js found: $NODE_VERSION" + +# Check npm +if ! command -v npm &> /dev/null; then + error_exit "npm is not installed" +fi +log_info "npm found: $(npm --version)" + +# Check .env files +if [[ ! -f "$SCRIPT_DIR/backend/.env" ]]; then + error_exit "Backend .env file not found at $SCRIPT_DIR/backend/.env" +fi +log_info "Backend .env file found" + +if [[ ! -f "$SCRIPT_DIR/frontend/.env" ]]; then + log_warn "Frontend .env file not found at $SCRIPT_DIR/frontend/.env (may use defaults)" +fi + +# Verify docker-compose.yml exists +if [[ ! -f "$SCRIPT_DIR/docker-compose.yml" ]]; then + error_exit "docker-compose.yml not found" +fi + +log_success "Pre-flight checks passed" + +# ----------------------------------------------------------------------------- +# Build Docker containers +# ----------------------------------------------------------------------------- +log_info "Building Docker containers..." +cd "$SCRIPT_DIR" + +$DOCKER_COMPOSE build --pull || error_exit "Docker build failed" + +log_success "Docker containers built" + +# ----------------------------------------------------------------------------- +# Restart Docker services +# ----------------------------------------------------------------------------- +log_info "Starting Docker services..." + +$DOCKER_COMPOSE up -d || error_exit "Failed to start Docker services" + +# Wait for postgres to be healthy +log_info "Waiting for PostgreSQL to be ready..." +POSTGRES_READY=false +for i in $(seq 1 $HEALTH_CHECK_RETRIES); do + if $DOCKER_COMPOSE exec -T postgres pg_isready -U apac_ops_bot &> /dev/null; then + POSTGRES_READY=true + break + fi + log_info "Waiting for PostgreSQL... (attempt $i/$HEALTH_CHECK_RETRIES)" + sleep $HEALTH_CHECK_INTERVAL +done + +if [[ "$POSTGRES_READY" != "true" ]]; then + error_exit "PostgreSQL failed to become ready" +fi + +log_success "Docker services started and PostgreSQL is ready" + +# ----------------------------------------------------------------------------- +# Run database migrations +# ----------------------------------------------------------------------------- +log_info "Running database migrations..." + +$DOCKER_COMPOSE exec -T backend alembic upgrade head || error_exit "Database migrations failed" + +log_success "Database migrations completed" + +# ----------------------------------------------------------------------------- +# Build frontend +# ----------------------------------------------------------------------------- +log_info "Building frontend..." +cd "$SCRIPT_DIR/frontend" + +# Clean install dependencies +log_info "Installing frontend dependencies..." +npm ci || error_exit "npm ci failed" + +# Build production bundle +log_info "Creating production build..." +npm run build || error_exit "Frontend build failed" + +if [[ ! -d "$SCRIPT_DIR/frontend/build" ]]; then + error_exit "Frontend build directory not found" +fi + +log_success "Frontend built successfully" + +# ----------------------------------------------------------------------------- +# Deploy frontend to Apache +# ----------------------------------------------------------------------------- +log_info "Deploying frontend to Apache..." + +# Create deployment directory if it doesn't exist +mkdir -p "$FRONTEND_DEPLOY_PATH" + +# Clear existing files +rm -rf "${FRONTEND_DEPLOY_PATH:?}"/* + +# Copy new build +cp -r "$SCRIPT_DIR/frontend/build/"* "$FRONTEND_DEPLOY_PATH/" + +# Set proper ownership +chown -R www-data:www-data "$FRONTEND_DEPLOY_PATH" + +# Verify index.html exists +if [[ ! -f "$FRONTEND_DEPLOY_PATH/index.html" ]]; then + error_exit "Frontend deployment verification failed - index.html not found" +fi + +log_success "Frontend deployed to $FRONTEND_DEPLOY_PATH" + +# ----------------------------------------------------------------------------- +# Verification +# ----------------------------------------------------------------------------- +log_info "Running verification checks..." + +# Wait for backend to be ready +log_info "Waiting for backend health check..." +BACKEND_READY=false +for i in $(seq 1 $HEALTH_CHECK_RETRIES); do + HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" "http://localhost:$BACKEND_PORT/health" 2>/dev/null || echo "000") + if [[ "$HTTP_STATUS" == "200" ]]; then + BACKEND_READY=true + break + fi + log_info "Waiting for backend... (attempt $i/$HEALTH_CHECK_RETRIES, status: $HTTP_STATUS)" + sleep $HEALTH_CHECK_INTERVAL +done + +if [[ "$BACKEND_READY" != "true" ]]; then + log_warn "Backend health check failed - service may still be starting" +else + log_success "Backend health check passed" +fi + +# Verify Docker containers are running +log_info "Docker container status:" +cd "$SCRIPT_DIR" +$DOCKER_COMPOSE ps + +# ----------------------------------------------------------------------------- +# Summary +# ----------------------------------------------------------------------------- +echo "" +echo "==============================================" +COMMIT_HASH=$(git rev-parse --short HEAD 2>/dev/null || echo "unknown") +COMMIT_MSG=$(git log -1 --pretty=format:"%s" 2>/dev/null || echo "unknown") + +if [[ "$BACKEND_READY" == "true" ]]; then + log_success "Deployment completed successfully!" +else + log_warn "Deployment completed with warnings (backend may still be starting)" +fi + +echo "" +log_info "Deployed commit: $COMMIT_HASH - $COMMIT_MSG" +log_info "Frontend path: $FRONTEND_DEPLOY_PATH" +log_info "Backend URL: http://localhost:$BACKEND_PORT" +echo "==============================================" diff --git a/docker-compose.yml b/docker-compose.yml index b2666a0..2d5d14b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -44,11 +44,11 @@ services: dockerfile: Dockerfile target: development container_name: apac_ops_bot_backend - command: uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload + command: uvicorn app.main:app --host 0.0.0.0 --port 8048 --reload volumes: - ./backend:/app ports: - - "8000:8000" + - "8048:8048" env_file: - ./backend/.env environment: @@ -78,8 +78,8 @@ services: env_file: - ./frontend/.env environment: - - REACT_APP_API_URL=http://83.151.203.105:8000/api/v1 - - REACT_APP_WS_URL=ws://83.151.203.105:8000/ws + - REACT_APP_API_URL=/apac-ops-bot-back/api/v1 + - REACT_APP_WS_URL=/apac-ops-bot-back/ws depends_on: - backend networks: diff --git a/frontend/.env.example b/frontend/.env.example index 760e289..4a33edf 100644 --- a/frontend/.env.example +++ b/frontend/.env.example @@ -1,6 +1,10 @@ # API Configuration -REACT_APP_API_URL=http://localhost:8000/api/v1 -REACT_APP_WS_URL=ws://localhost:8000/ws +# For production with Apache reverse proxy: +REACT_APP_API_URL=/apac-ops-bot-back/api/v1 +REACT_APP_WS_URL=/apac-ops-bot-back/ws +# For local development without proxy: +# REACT_APP_API_URL=http://localhost:8048/api/v1 +# REACT_APP_WS_URL=ws://localhost:8048/ws # Azure AD / MSAL Configuration REACT_APP_AZURE_CLIENT_ID=your-client-id-here diff --git a/frontend/src/services/api.ts b/frontend/src/services/api.ts index e6cad1a..a8c3530 100644 --- a/frontend/src/services/api.ts +++ b/frontend/src/services/api.ts @@ -6,7 +6,7 @@ import axios, { AxiosInstance, InternalAxiosRequestConfig } from 'axios'; -const API_URL = process.env.REACT_APP_API_URL || 'http://localhost:8000/api/v1'; +const API_URL = process.env.REACT_APP_API_URL || '/apac-ops-bot-back/api/v1'; // Create axios instance const apiClient: AxiosInstance = axios.create({