#!/usr/bin/env bash # # deploy.sh — Idempotent deployment script for PDF Accessibility Checker # # Usage: # cd /opt/pdf-accessibility && ./deploy.sh # # Architecture: # - Apache (host) serves frontend + api.php from /var/www/html/pdf-accessibility # - Docker Compose runs: worker (Python), Redis, PostgreSQL # - Redis/PostgreSQL exposed on localhost for api.php access # set -euo pipefail # ── Configuration ───────────────────────────────────────────────── REPO_DIR="$(cd "$(dirname "$0")" && pwd)" WEB_DIR="/var/www/html/pdf-accessibility" COMPOSE_FILE="docker-compose.prod.yml" ENV_FILE="${REPO_DIR}/.env" MIN_PHP_VERSION="8.0" # Colors RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' log() { echo -e "${GREEN}[DEPLOY]${NC} $*"; } warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } err() { echo -e "${RED}[ERROR]${NC} $*"; } # ── Preflight Checks ───────────────────────────────────────────── log "Starting deployment from ${REPO_DIR}" # Check Docker if ! command -v docker &>/dev/null; then err "Docker is not installed. Install it first:" err " curl -fsSL https://get.docker.com | sh" err " sudo usermod -aG docker \$USER" exit 1 fi # Check Docker Compose (v2 plugin) if ! docker compose version &>/dev/null; then err "Docker Compose v2 is not available. Install it:" err " sudo apt-get install docker-compose-plugin" exit 1 fi # Check PHP if ! command -v php &>/dev/null; then warn "PHP is not installed. api.php requires PHP ${MIN_PHP_VERSION}+ with extensions:" warn " sudo apt-get install php8.2 php8.2-redis php8.2-pgsql php8.2-curl php8.2-mbstring" else PHP_VER=$(php -r 'echo PHP_MAJOR_VERSION . "." . PHP_MINOR_VERSION;') log "PHP version: ${PHP_VER}" # Check required extensions MISSING_EXT="" php -m | grep -qi redis || MISSING_EXT="${MISSING_EXT} php-redis" php -m | grep -qi pgsql || MISSING_EXT="${MISSING_EXT} php-pgsql" php -m | grep -qi curl || MISSING_EXT="${MISSING_EXT} php-curl" if [ -n "${MISSING_EXT}" ]; then warn "Missing PHP extensions:${MISSING_EXT}" warn "Install with: sudo apt-get install${MISSING_EXT}" fi fi # ── Pull Latest Code ───────────────────────────────────────────── log "Pulling latest code..." cd "${REPO_DIR}" if [ -d .git ]; then git fetch --all git reset --hard origin/$(git rev-parse --abbrev-ref HEAD) log "Code updated to $(git log --oneline -1)" else warn "Not a git repo — using existing files" fi # ── Environment File ───────────────────────────────────────────── if [ ! -f "${ENV_FILE}" ]; then log "Creating .env from .env.example (first run)..." cp "${REPO_DIR}/.env.example" "${ENV_FILE}" # Override Docker hostnames with localhost for host-side PHP # (Worker uses Docker internal names via docker-compose.prod.yml) sed -i 's/^DB_HOST=postgres/DB_HOST=127.0.0.1/' "${ENV_FILE}" sed -i 's/^REDIS_HOST=redis/REDIS_HOST=127.0.0.1/' "${ENV_FILE}" sed -i 's/^DEV_MODE=true/DEV_MODE=false/' "${ENV_FILE}" warn "Review and update ${ENV_FILE} with production values:" warn " - DB_PASSWORD (change from default!)" warn " - ANTHROPIC_API_KEY" warn " - GOOGLE_API_KEY" warn " - AZURE_* settings" else log "Using existing .env file" fi # ── Build Docker Containers ────────────────────────────────────── log "Building Docker containers (using cache)..." docker compose -f "${COMPOSE_FILE}" build log "Starting/restarting Docker services..." docker compose -f "${COMPOSE_FILE}" up -d --remove-orphans # Wait for PostgreSQL to be ready log "Waiting for PostgreSQL to be healthy..." RETRIES=30 until docker compose -f "${COMPOSE_FILE}" exec -T postgres pg_isready -U pdf_checker &>/dev/null || [ $RETRIES -eq 0 ]; do sleep 1 RETRIES=$((RETRIES - 1)) done if [ $RETRIES -eq 0 ]; then err "PostgreSQL failed to start. Check logs:" err " docker compose -f ${COMPOSE_FILE} logs postgres" exit 1 fi log "PostgreSQL is ready" # Database init.sql runs automatically on first compose up via # /docker-entrypoint-initdb.d/init.sql — no migration tool needed. # For future migrations, add numbered SQL files to db/ and apply: if [ -d "${REPO_DIR}/db/migrations" ]; then for migration in "${REPO_DIR}"/db/migrations/*.sql; do [ -f "$migration" ] || continue MIGRATION_NAME=$(basename "$migration") log "Applying migration: ${MIGRATION_NAME}" docker compose -f "${COMPOSE_FILE}" exec -T postgres \ psql -U pdf_checker -d pdf_checker -f "/dev/stdin" < "$migration" 2>/dev/null || \ warn "Migration ${MIGRATION_NAME} may have already been applied" done fi # ── Deploy Frontend Files ───────────────────────────────────────── log "Deploying frontend to ${WEB_DIR}..." # Create web directory if it doesn't exist sudo mkdir -p "${WEB_DIR}" # Clean old frontend files (but preserve uploads, results, .env, logs) log "Cleaning old frontend files..." sudo rm -f "${WEB_DIR}/index.html" sudo rm -rf "${WEB_DIR}/css" "${WEB_DIR}/js" sudo rm -f "${WEB_DIR}/api.php" "${WEB_DIR}/auth.php" # Copy frontend files sudo cp "${REPO_DIR}/index.html" "${WEB_DIR}/" sudo cp -r "${REPO_DIR}/css" "${WEB_DIR}/" sudo cp -r "${REPO_DIR}/js" "${WEB_DIR}/" # Copy PHP backend files sudo cp "${REPO_DIR}/api.php" "${WEB_DIR}/" sudo cp "${REPO_DIR}/auth.php" "${WEB_DIR}/" # Copy Python scripts (needed if api.php fallback exec() is used) sudo cp "${REPO_DIR}/enterprise_pdf_checker.py" "${WEB_DIR}/" sudo cp "${REPO_DIR}/pdf_remediation.py" "${WEB_DIR}/" sudo cp "${REPO_DIR}/logger_config.py" "${WEB_DIR}/" sudo cp "${REPO_DIR}/retry_helper.py" "${WEB_DIR}/" # Copy .env for PHP (if not already there) if [ ! -f "${WEB_DIR}/.env" ]; then sudo cp "${ENV_FILE}" "${WEB_DIR}/.env" log "Copied .env to web directory" else # Update .env in web dir from repo .env sudo cp "${ENV_FILE}" "${WEB_DIR}/.env" fi # Create runtime directories sudo mkdir -p "${WEB_DIR}/uploads" "${WEB_DIR}/results" "${WEB_DIR}/logs" # Set ownership for Apache sudo chown -R www-data:www-data "${WEB_DIR}" sudo chmod -R 755 "${WEB_DIR}" sudo chmod -R 775 "${WEB_DIR}/uploads" "${WEB_DIR}/results" "${WEB_DIR}/logs" # ── Verify ──────────────────────────────────────────────────────── log "" log "=============================================" log " Deployment complete!" log "=============================================" log "" log "Services status:" docker compose -f "${COMPOSE_FILE}" ps --format "table {{.Name}}\t{{.Status}}\t{{.Ports}}" log "" log "Frontend: ${WEB_DIR}" log "Docker: worker + Redis (127.0.0.1:6379) + PostgreSQL (127.0.0.1:5432)" log "" # Quick health check if curl -sf http://127.0.0.1:6379 &>/dev/null || redis-cli -h 127.0.0.1 ping &>/dev/null 2>&1; then log "Redis: OK" fi if docker compose -f "${COMPOSE_FILE}" exec -T postgres pg_isready -U pdf_checker &>/dev/null; then log "PostgreSQL: OK" fi log "" log "Next steps:" log " 1. Configure Apache vhost for https://ai-sandbox.oliver.solutions/pdf-accessibility" log " 2. Review ${WEB_DIR}/.env (especially DB_PASSWORD and API keys)" log " 3. Restart Apache: sudo systemctl reload apache2" log ""