When deploy.sh runs via sudo, git tried to use root's SSH key which doesn't have Bitbucket access. Now detects repo owner and runs git commands as that user so the user's SSH key is used. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
225 lines
8.2 KiB
Bash
Executable file
225 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"
|
|
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" "${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 ""
|