pdf-accessibility/deploy.sh
Vadym Samoilenko 62094f4dfa Move document history to separate history.html page
- history.html: standalone page with My Documents table + auth
- js/history.js: renderHistory, loadHistory, deleteHistoryJob logic
- js/app-history.js: MSAL auth init for history.html
- index.html: remove history section, add 'My Documents' link in header
- js/app.js: show historyLink after auth, open job from ?job_id= URL param
- deploy.sh: include history.html in deploy

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 15:21:19 +00:00

226 lines
8.2 KiB
Bash
Executable file

#!/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: PostgreSQL
# - PDF processing via Google Cloud Run (synchronous HTTP call from api.php)
#
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-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 pgsql || MISSING_EXT="${MISSING_EXT} php-pgsql"
php -m | grep -qi curl || MISSING_EXT="${MISSING_EXT} php-curl"
php -m | grep -qi openssl || MISSING_EXT="${MISSING_EXT} php-openssl"
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 config core.fileMode false
# Run git as the repo owner (not root) so SSH keys work
REPO_OWNER=$(stat -c '%U' "${REPO_DIR}/.git")
if [ "$(id -u)" = "0" ] && [ "${REPO_OWNER}" != "root" ]; then
sudo -u "${REPO_OWNER}" git -C "${REPO_DIR}" fetch --all
sudo -u "${REPO_OWNER}" git -C "${REPO_DIR}" reset --hard origin/$(git rev-parse --abbrev-ref HEAD)
else
git fetch --all
git reset --hard origin/$(git rev-parse --abbrev-ref HEAD)
fi
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
sed -i 's/^DB_HOST=postgres/DB_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 " - CLOUD_RUN_URL"
warn " - GCP_SA_KEY_PATH (copy pdf-api-invoker-key.json to server)"
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" "${WEB_DIR}/history.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 "${REPO_DIR}/history.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" "${WEB_DIR}/rate_limits"
# 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" "${WEB_DIR}/rate_limits"
# ── 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: PostgreSQL (127.0.0.1:1221)"
log "Cloud Run: ${CLOUD_RUN_URL:-$(grep '^CLOUD_RUN_URL=' "${ENV_FILE}" 2>/dev/null | cut -d= -f2 || echo 'not set')}"
log ""
# Quick health check
if docker compose -f "${COMPOSE_FILE}" exec -T postgres pg_isready -U pdf_checker &>/dev/null; then
log "PostgreSQL: OK"
fi
log ""
log "Reloading Apache..."
sudo systemctl reload apache2 && log "Apache reloaded" || warn "Apache reload failed — run: sudo systemctl reload apache2"
log ""
log "Next steps (if first deploy):"
log " 1. Ensure pdf-api-invoker-key.json is at the GCP_SA_KEY_PATH location"
log " 2. Review ${WEB_DIR}/.env (especially CLOUD_RUN_URL and API keys)"
log ""