#!/usr/bin/env bash set -euo pipefail APP_NAME="${APP_NAME:-ai-agent-admin}" PROJECT_DIR="${PROJECT_DIR:-/opt/ai-agent-admin/current}" BACKEND_DIR="${BACKEND_DIR:-${PROJECT_DIR}/backend-fastapi}" WEB_DIST_DIR="${WEB_DIST_DIR:-${PROJECT_DIR}/web/apps/web-ele/dist}" WEB_ROOT="${WEB_ROOT:-/var/www/ai-agent-admin}" SERVICE_NAME="${SERVICE_NAME:-ai-agent-admin}" PYTHON_BIN="${PYTHON_BIN:-python3}" ENV_FILE="${ENV_FILE:-${BACKEND_DIR}/env/prod.env}" BACKEND_PORT="${BACKEND_PORT:-8001}" RUN_MIGRATIONS="${RUN_MIGRATIONS:-1}" IMPORT_INIT_DATA="${IMPORT_INIT_DATA:-auto}" SEED_MULTICA_AGENTS="${SEED_MULTICA_AGENTS:-1}" BUILD_WEB="${BUILD_WEB:-0}" VERIFY_RUNTIME="${VERIFY_RUNTIME:-1}" VERIFY_BASE_URL="${VERIFY_BASE_URL:-http://127.0.0.1:${BACKEND_PORT}}" VERIFY_USERNAME="${VERIFY_USERNAME:-zhangwei}" VERIFY_PASSWORD="${VERIFY_PASSWORD:-123456}" VERIFY_ATTEMPTS="${VERIFY_ATTEMPTS:-6}" VERIFY_SLEEP_SECONDS="${VERIFY_SLEEP_SECONDS:-5}" log() { printf '[%s] %s\n' "${APP_NAME}" "$*" } require_file() { if [ ! -f "$1" ]; then printf 'Missing required file: %s\n' "$1" >&2 exit 1 fi } require_dir() { if [ ! -d "$1" ]; then printf 'Missing required directory: %s\n' "$1" >&2 exit 1 fi } require_dir "${PROJECT_DIR}" require_dir "${BACKEND_DIR}" require_file "${ENV_FILE}" if [ "${BUILD_WEB}" = "1" ]; then log "building web-ele" (cd "${PROJECT_DIR}/web" && pnpm --filter @vben/web-ele build) fi require_file "${WEB_DIST_DIR}/index.html" log "publishing web dist to ${WEB_ROOT}" install -d "${WEB_ROOT}" rsync -a --delete "${WEB_DIST_DIR}/" "${WEB_ROOT}/" log "preparing backend venv" cd "${BACKEND_DIR}" if [ ! -x ".venv/bin/python" ]; then "${PYTHON_BIN}" -m venv .venv fi # shellcheck source=/dev/null . .venv/bin/activate python -m pip install --upgrade pip python -m pip install -r requirements.txt if [ "${RUN_MIGRATIONS}" = "1" ]; then log "running alembic migrations" ENV=prod python -m alembic upgrade head fi if [ "${IMPORT_INIT_DATA}" = "auto" ]; then USER_COUNT="$( ENV=prod python - <<'PY' import asyncio from sqlalchemy import text from app.database import AsyncSessionLocal async def main(): try: async with AsyncSessionLocal() as db: result = await db.execute(text("SELECT COUNT(*) FROM core_user")) print(result.scalar() or 0) except Exception: print(0) asyncio.run(main()) PY )" if [ "${USER_COUNT}" = "0" ] && [ -f "db_init.json" ]; then log "importing initial data" ENV=prod python scripts/loaddata.py db_init.json fi fi if [ "${SEED_MULTICA_AGENTS}" = "1" ] && [ -f "scripts/seed_multica_org_agents.py" ]; then log "seeding Multica organization agents" ENV=prod python scripts/seed_multica_org_agents.py --apply fi log "restarting ${SERVICE_NAME}" if command -v systemctl >/dev/null 2>&1; then systemctl daemon-reload systemctl restart "${SERVICE_NAME}" fi if command -v nginx >/dev/null 2>&1; then log "reloading nginx" nginx -t if command -v systemctl >/dev/null 2>&1; then systemctl reload nginx else nginx -s reload fi fi if [ "${VERIFY_RUNTIME}" = "1" ]; then log "verifying AI Agent Admin runtime" for attempt in $(seq 1 "${VERIFY_ATTEMPTS}"); do if ENV=prod python scripts/verify_ai_agent_admin.py \ --base-url "${VERIFY_BASE_URL}" \ --username "${VERIFY_USERNAME}" \ --password "${VERIFY_PASSWORD}"; then break fi if [ "${attempt}" = "${VERIFY_ATTEMPTS}" ]; then exit 1 fi log "runtime verification failed, retrying in ${VERIFY_SLEEP_SECONDS}s (${attempt}/${VERIFY_ATTEMPTS})" sleep "${VERIFY_SLEEP_SECONDS}" done fi log "done: backend port ${BACKEND_PORT}, web root ${WEB_ROOT}"