pdf-accessibility/deploy.sh
Vadym Samoilenko 0622a86bbd deploy.sh: run git pull as repo owner, not root (fixes SSH key auth)
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>
2026-03-13 15:02:23 +00:00

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 ""