diff --git a/.env.production.example b/.env.production.example index a843a33..c16fbba 100644 --- a/.env.production.example +++ b/.env.production.example @@ -25,6 +25,7 @@ STORAGE_ROOT=/storage # LLM Model LLM_MODEL=claude-sonnet-4-6 -# Frontend (empty = same-origin behind nginx) -NEXT_PUBLIC_API_URL= +# Frontend (set at Docker build time via docker-compose.prod.yml) +NEXT_PUBLIC_API_URL=/amazon-transcreation NEXT_PUBLIC_WS_URL= +NEXT_PUBLIC_BASE_PATH=/amazon-transcreation diff --git a/apache/amazon-transcreation.conf b/apache/amazon-transcreation.conf new file mode 100644 index 0000000..c0bcb5e --- /dev/null +++ b/apache/amazon-transcreation.conf @@ -0,0 +1,44 @@ +# ============================================================ +# Amazon Transcreation Platform — Apache Reverse Proxy +# ============================================================ +# Add this inside your existing block for +# optical-dev.oliver.solutions, or Include this file from it. +# +# Required Apache modules (enable if not already): +# sudo a2enmod proxy proxy_http proxy_wstunnel rewrite +# sudo systemctl restart apache2 +# ============================================================ + +# --- API + Backend routes --- +ProxyPass /amazon-transcreation/api http://127.0.0.1:8040/api +ProxyPassReverse /amazon-transcreation/api http://127.0.0.1:8040/api + +ProxyPass /amazon-transcreation/health http://127.0.0.1:8040/health +ProxyPassReverse /amazon-transcreation/health http://127.0.0.1:8040/health + +ProxyPass /amazon-transcreation/docs http://127.0.0.1:8040/docs +ProxyPassReverse /amazon-transcreation/docs http://127.0.0.1:8040/docs + +ProxyPass /amazon-transcreation/openapi.json http://127.0.0.1:8040/openapi.json +ProxyPassReverse /amazon-transcreation/openapi.json http://127.0.0.1:8040/openapi.json + +# --- WebSocket (job monitoring) --- +RewriteEngine On +RewriteCond %{HTTP:Upgrade} websocket [NC] +RewriteCond %{HTTP:Connection} upgrade [NC] +RewriteRule ^/amazon-transcreation/ws/(.*) ws://127.0.0.1:8040/ws/$1 [P,L] + +ProxyPass /amazon-transcreation/ws http://127.0.0.1:8040/ws +ProxyPassReverse /amazon-transcreation/ws http://127.0.0.1:8040/ws + +# --- Frontend (Next.js) --- +# Must come AFTER the more specific /api, /ws, /health rules +ProxyPass /amazon-transcreation http://127.0.0.1:3050/amazon-transcreation +ProxyPassReverse /amazon-transcreation http://127.0.0.1:3050/amazon-transcreation + +# Increase timeouts for long-running agent API calls +ProxyTimeout 300 + +# Pass original host header +ProxyPreserveHost On +RequestHeader set X-Forwarded-Proto "https" diff --git a/deploy.sh b/deploy.sh index 4c2e1f3..f55af2d 100755 --- a/deploy.sh +++ b/deploy.sh @@ -7,7 +7,8 @@ # Updates: ./deploy.sh # Full rebuild: ./deploy.sh --rebuild # -# Expected location: /opt/amazon-transcreation +# Location: /opt/amazon-transcreation +# URL: https://optical-dev.oliver.solutions/amazon-transcreation # ============================================================ set -euo pipefail @@ -39,7 +40,7 @@ for arg in "$@"; do --rebuild) REBUILD=true ;; --help) echo "Usage: $0 [--init] [--rebuild]" - echo " --init First-time setup (create .env, run migrations, seed data)" + echo " --init First-time setup (create .env, run migrations, seed data, configure Apache)" echo " --rebuild Force rebuild all Docker images (no cache)" echo " (no args) Pull latest code, rebuild changed images, restart" exit 0 @@ -69,10 +70,10 @@ if [ "$INIT" = true ]; then cp .env.production.example .env # Generate a real JWT secret JWT_SECRET=$(python3 -c "import secrets; print(secrets.token_hex(32))" 2>/dev/null || openssl rand -hex 32) - sed -i.bak "s/CHANGE_ME_GENERATE_WITH.*/$JWT_SECRET/" .env && rm -f .env.bak + sed -i "s/CHANGE_ME_GENERATE_WITH.*/$JWT_SECRET/" .env # Generate a real DB password DB_PASS=$(python3 -c "import secrets; print(secrets.token_hex(16))" 2>/dev/null || openssl rand -hex 16) - sed -i.bak "s/CHANGE_ME_DB_PASSWORD/$DB_PASS/g" .env && rm -f .env.bak + sed -i "s/CHANGE_ME_DB_PASSWORD/$DB_PASS/g" .env warn ".env created from template. You MUST edit it to set:" warn " - ANTHROPIC_API_KEY (your Claude API key)" warn "" @@ -95,7 +96,6 @@ if [ "$INIT" = true ]; then # Create storage directories log "Creating storage directories..." mkdir -p storage/amazon/{tm,ref} - mkdir -p nginx/ssl # Build all images log "Building all Docker images (this may take a few minutes)..." @@ -110,11 +110,12 @@ if [ "$INIT" = true ]; then # Start backend for migrations log "Starting backend..." $COMPOSE up -d backend + sleep 5 # Wait for backend to be ready log "Waiting for backend to start..." for i in $(seq 1 30); do - if $COMPOSE exec -T backend python -c "print('ok')" >/dev/null 2>&1; then + if curl -sf http://127.0.0.1:8040/health >/dev/null 2>&1; then break fi sleep 2 @@ -131,8 +132,8 @@ if [ "$INIT" = true ]; then --source "../Agent build + supporting JSONs/JSON REFS and TMs" \ --target storage/amazon else - warn "Reference files source directory not found. Skipping import." - warn "You can run this manually later:" + warn "Reference files source directory not found at ../Agent build + supporting JSONs/JSON REFS and TMs" + warn "You can import them manually later:" warn " python3 seed/import_reference_files.py --source /path/to/JSON_REFS_and_TMs --target storage/amazon" fi @@ -145,14 +146,61 @@ if [ "$INIT" = true ]; then log "Starting all services..." $COMPOSE up -d + # --------------------------------------------------------------- + # Apache configuration + # --------------------------------------------------------------- + log "Configuring Apache reverse proxy..." + + # Enable required modules + a2enmod proxy proxy_http proxy_wstunnel rewrite headers 2>/dev/null || true + + # Find the SSL vhost for optical-dev.oliver.solutions + VHOST_FILE=$(grep -rl "optical-dev.oliver.solutions" /etc/apache2/sites-enabled/ 2>/dev/null | head -1 || true) + + if [ -n "$VHOST_FILE" ]; then + # Check if already configured + if grep -q "amazon-transcreation" "$VHOST_FILE" 2>/dev/null; then + log "Apache already has amazon-transcreation config in $VHOST_FILE" + else + warn "Found vhost at: $VHOST_FILE" + warn "" + warn "Add the following INSIDE the block, before :" + warn "" + warn " Include /opt/amazon-transcreation/apache/amazon-transcreation.conf" + warn "" + warn "Then run: sudo systemctl reload apache2" + echo "" + read -p "Want me to add it automatically? [y/N] " -n 1 -r + echo + if [[ $REPLY =~ ^[Yy]$ ]]; then + # Insert Include line before + sed -i "/<\/VirtualHost>/i\\ Include /opt/amazon-transcreation/apache/amazon-transcreation.conf" "$VHOST_FILE" + systemctl reload apache2 + log "Apache config added and reloaded." + fi + fi + else + warn "Could not find Apache vhost for optical-dev.oliver.solutions" + warn "Manually add this to your SSL vhost:" + warn " Include /opt/amazon-transcreation/apache/amazon-transcreation.conf" + warn "Then: sudo systemctl reload apache2" + fi + + # Health check + sleep 3 + if curl -sf http://127.0.0.1:8040/health >/dev/null 2>&1; then + log "Backend health check passed" + else + warn "Backend health check failed — check logs: $COMPOSE logs backend" + fi + echo "" log "=== SETUP COMPLETE ===" log "" - log "Services:" - log " Frontend: http://localhost (via nginx)" - log " API: http://localhost/api/v1" - log " API Docs: http://localhost/docs" - log " Health: http://localhost/health" + log "URL: https://optical-dev.oliver.solutions/amazon-transcreation" + log "" + log "Services running:" + $COMPOSE ps --format "table {{.Name}}\t{{.Status}}\t{{.Ports}}" log "" log "Test credentials:" log " admin@amazon.com / admin123!" @@ -160,9 +208,9 @@ if [ "$INIT" = true ]; then log " reviewer@amazon.com / reviewer123!" log "" log "Next steps:" - log " 1. Update ANTHROPIC_API_KEY in .env if not done" - log " 2. Change default user passwords via the admin panel" - log " 3. For HTTPS, add certs to nginx/ssl/ and uncomment the SSL block in nginx/nginx.conf" + log " 1. Verify ANTHROPIC_API_KEY is set in .env" + log " 2. Ensure Apache config is loaded (see above)" + log " 3. Change default passwords via admin panel" exit 0 fi @@ -197,19 +245,16 @@ log "Restarting all services..." $COMPOSE up -d # Health check -log "Waiting for services to start..." +log "Waiting for services..." sleep 5 -HEALTH=$(curl -sf http://localhost/health 2>/dev/null || echo '{"status":"error"}') -if echo "$HEALTH" | grep -q '"healthy"'; then +if curl -sf http://127.0.0.1:8040/health >/dev/null 2>&1; then log "Health check passed" else - warn "Health check returned: $HEALTH" - warn "Check logs: docker compose -f $COMPOSE_FILE logs --tail 50" + warn "Health check failed — check: $COMPOSE logs --tail 50" fi -# Show running containers echo "" -$COMPOSE ps +$COMPOSE ps --format "table {{.Name}}\t{{.Status}}\t{{.Ports}}" echo "" -log "Deploy complete." +log "Deploy complete. URL: https://optical-dev.oliver.solutions/amazon-transcreation" diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index d3ae7f1..33ca149 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -68,28 +68,16 @@ services: context: ./frontend dockerfile: Dockerfile args: - - NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL:-/api} - - NEXT_PUBLIC_WS_URL=${NEXT_PUBLIC_WS_URL:-} + - NEXT_PUBLIC_API_URL=/amazon-transcreation + - NEXT_PUBLIC_WS_URL= + - NEXT_PUBLIC_BASE_PATH=/amazon-transcreation restart: unless-stopped ports: - - "127.0.0.1:3000:3000" + - "127.0.0.1:3050:3000" environment: - NODE_ENV=production depends_on: - backend - nginx: - image: nginx:alpine - restart: unless-stopped - ports: - - "80:80" - - "443:443" - volumes: - - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro - - ./nginx/ssl:/etc/nginx/ssl:ro - depends_on: - - frontend - - backend - volumes: pgdata: diff --git a/frontend/Dockerfile b/frontend/Dockerfile index a1fea18..033115a 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -1,11 +1,13 @@ FROM node:20-alpine AS builder WORKDIR /app -# Accept build-time env vars for API URL -ARG NEXT_PUBLIC_API_URL=http://localhost:8040 -ARG NEXT_PUBLIC_WS_URL=ws://localhost:8040 +# Accept build-time env vars +ARG NEXT_PUBLIC_API_URL= +ARG NEXT_PUBLIC_WS_URL= +ARG NEXT_PUBLIC_BASE_PATH= ENV NEXT_PUBLIC_API_URL=$NEXT_PUBLIC_API_URL ENV NEXT_PUBLIC_WS_URL=$NEXT_PUBLIC_WS_URL +ENV NEXT_PUBLIC_BASE_PATH=$NEXT_PUBLIC_BASE_PATH COPY package.json package-lock.json* ./ RUN npm ci diff --git a/frontend/next.config.js b/frontend/next.config.js index 275d14a..296e0cf 100644 --- a/frontend/next.config.js +++ b/frontend/next.config.js @@ -1,8 +1,10 @@ /** @type {import('next').NextConfig} */ const nextConfig = { output: 'standalone', + basePath: process.env.NEXT_PUBLIC_BASE_PATH || '', + assetPrefix: process.env.NEXT_PUBLIC_BASE_PATH || '', images: { - domains: ['localhost'], + domains: ['localhost', 'optical-dev.oliver.solutions'], }, }; diff --git a/frontend/src/lib/ws.ts b/frontend/src/lib/ws.ts index 3a5b88f..3bfb301 100644 --- a/frontend/src/lib/ws.ts +++ b/frontend/src/lib/ws.ts @@ -3,8 +3,19 @@ import { getToken } from "./auth"; type MessageHandler = (message: WSMessage) => void; -const WS_BASE_URL = - process.env.NEXT_PUBLIC_WS_URL || "ws://localhost:8000"; +function getWsBaseUrl(): string { + // If explicit WS URL is set, use it + const explicit = process.env.NEXT_PUBLIC_WS_URL; + if (explicit) return explicit; + + // Auto-detect from current page location (works behind reverse proxy) + if (typeof window !== "undefined") { + const proto = window.location.protocol === "https:" ? "wss:" : "ws:"; + return `${proto}//${window.location.host}`; + } + + return "ws://localhost:8040"; +} export class WebSocketClient { private ws: WebSocket | null = null; @@ -17,7 +28,8 @@ export class WebSocketClient { constructor(jobId: string) { const token = getToken(); - this.url = `${WS_BASE_URL}/ws/jobs/${jobId}?token=${token}`; + const basePath = process.env.NEXT_PUBLIC_BASE_PATH || ""; + this.url = `${getWsBaseUrl()}${basePath}/ws/jobs/${jobId}?token=${token}`; } connect(): void {