run_dataops.sh 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
  1. #!/usr/bin/env bash
  2. set -euo pipefail
  3. APP_DIR="${APP_DIR:-/opt/dataops-platform}"
  4. ENV_FILE="${APP_ENV_FILE:-/etc/dataops-platform/dataops.env}"
  5. LOG_DIR="${APP_DIR}/logs"
  6. VENV_DIR="${APP_DIR}/venv"
  7. mkdir -p "${LOG_DIR}"
  8. if [[ -f "${ENV_FILE}" ]]; then
  9. if [[ ! -r "${ENV_FILE}" ]]; then
  10. echo "无法读取环境变量文件: ${ENV_FILE} (用户: $(id -un))" >&2
  11. echo "请执行: sudo chown root:ubuntu ${ENV_FILE} && sudo chmod 640 ${ENV_FILE}" >&2
  12. exit 1
  13. fi
  14. set -a
  15. # shellcheck disable=SC1090
  16. source <(sed 's/\r$//' "${ENV_FILE}" | sed '1s/^\xEF\xBB\xBF//')
  17. set +a
  18. fi
  19. if [[ -z "${DEEPSEEK_API_KEY:-}" && -z "${LLM_API_KEY:-}" ]]; then
  20. echo "[run_dataops] WARN: DEEPSEEK_API_KEY 未从 ${ENV_FILE} 加载" >&2
  21. fi
  22. export FLASK_ENV="${FLASK_ENV:-production}"
  23. export APP_ENV_FILE="${ENV_FILE}"
  24. export APP_DIR="${APP_DIR:-/opt/dataops-platform}"
  25. export PYTHONPATH="${APP_DIR}${PYTHONPATH:+:${PYTHONPATH}}"
  26. LISTEN_HOST="${LISTEN_HOST:-0.0.0.0}"
  27. LISTEN_PORT="${LISTEN_PORT:-5500}"
  28. GUNICORN_WORKERS="${GUNICORN_WORKERS:-4}"
  29. GUNICORN_TIMEOUT="${GUNICORN_TIMEOUT:-120}"
  30. # 与 Flask ProductionConfig.PORT 保持一致
  31. export PORT="${PORT:-${LISTEN_PORT}}"
  32. # 统一写入绝对路径,避免相对路径落到 APP_DIR 根目录
  33. export LOG_DIR="${LOG_DIR:-${APP_DIR}/logs}"
  34. mkdir -p "${LOG_DIR}"
  35. if [[ -z "${LOG_FILE:-}" || "${LOG_FILE}" != /* ]]; then
  36. log_filename="${LOG_FILE:-flask_production.log}"
  37. log_filename="${log_filename##*/}"
  38. export LOG_FILE="${LOG_DIR}/${log_filename}"
  39. fi
  40. cd "${APP_DIR}"
  41. if [[ ! -x "${VENV_DIR}/bin/gunicorn" ]]; then
  42. echo "gunicorn 未安装: ${VENV_DIR}/bin/gunicorn" >&2
  43. exit 1
  44. fi
  45. if [[ ! -f "${APP_DIR}/alembic.ini" || ! -d "${APP_DIR}/migrations" ]]; then
  46. echo "缺少数据库迁移文件: ${APP_DIR}/alembic.ini 或 migrations/" >&2
  47. exit 1
  48. fi
  49. echo "[run_dataops] applying database migrations" >&2
  50. role_init_url="${DB_ROLE_INIT_DATABASE_URL:?DB_ROLE_INIT_DATABASE_URL is required for one-shot role bootstrap}"
  51. migration_url="${MIGRATION_DATABASE_URL:?MIGRATION_DATABASE_URL is required}"
  52. runtime_url="${DATABASE_URL:?DATABASE_URL runtime connection is required}"
  53. if [[ "${role_init_url}" == "${runtime_url}" || "${migration_url}" == "${runtime_url}" ]]; then
  54. echo "role-init/migration and runtime URLs must use different database identities" >&2
  55. exit 1
  56. fi
  57. DB_ROLE_INIT_DATABASE_URL="${role_init_url}" \
  58. "${VENV_DIR}/bin/python" -m app.core.edge_gateway.runtime_roles
  59. MIGRATION_DATABASE_URL="${migration_url}" \
  60. "${VENV_DIR}/bin/alembic" -c "${APP_DIR}/alembic.ini" upgrade head
  61. unset DB_ROLE_INIT_DATABASE_URL MIGRATION_DATABASE_URL SQLALCHEMY_DATABASE_URI
  62. unset role_init_url migration_url
  63. echo "[run_dataops] binding ${LISTEN_HOST}:${LISTEN_PORT} workers=${GUNICORN_WORKERS} log=${LOG_FILE}" >&2
  64. if [[ ! -f "${APP_DIR}/gunicorn_config.py" ]]; then
  65. echo "缺少 gunicorn 配置: ${APP_DIR}/gunicorn_config.py" >&2
  66. exit 1
  67. fi
  68. # 启动前验证 Flask 应用能正常加载(失败时错误写入 supervisor 日志)
  69. if ! "${VENV_DIR}/bin/python" -c "from app import create_app; create_app(); print('app bootstrap ok')" >&2; then
  70. echo "Flask 应用预检失败,请检查 ${ENV_FILE} 与依赖配置" >&2
  71. exit 1
  72. fi
  73. exec "${VENV_DIR}/bin/gunicorn" \
  74. --config "${APP_DIR}/gunicorn_config.py" \
  75. --bind "${LISTEN_HOST}:${LISTEN_PORT}" \
  76. --workers "${GUNICORN_WORKERS}" \
  77. --timeout "${GUNICORN_TIMEOUT}" \
  78. --access-logfile "${LOG_DIR}/gunicorn_access.log" \
  79. --error-logfile "${LOG_DIR}/gunicorn_error.log" \
  80. --capture-output \
  81. --enable-stdio-inheritance \
  82. --log-level info \
  83. wsgi:application