finai/deploy/setup-backend.sh

247 lines
8.5 KiB
Bash
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
# 财务大模型智能问答客服系统 — 后端一键落地脚本
#
# 前置:
# - Ubuntu 24.04Python 3.12、postgresql、redis-server、nginx 已通过 apt 安装
# - 代码已放在 /home/ubuntu/finai/{backend,frontend,miniapp}
# - 用 ubuntu 账号执行本脚本;脚本内需要 root 权限的步骤会自己 sudo
#
# 用法:
# bash /home/ubuntu/finai/deploy/setup-backend.sh
# 可选环境变量:
# DOMAIN=finai.ctc-zc.com 站点域名(写进 ALLOWED_ORIGINS
# DB_NAME=finai PG 库名 = 用户名
# DB_PASS=<留空自动生成> PG 用户密码
# FORCE_ENV=1 即使 .env 已存在也覆盖重写
#
# 幂等性:
# venv、依赖、PG 用户/库、.env、systemd unit 均检查后再动作,重复执行安全。
set -Eeuo pipefail
# ─── 可调参数 ──────────────────────────────────────────────
FINAI_HOME="/home/ubuntu/finai"
BACKEND_DIR="${FINAI_HOME}/backend"
STORAGE_DIR="${BACKEND_DIR}/storage"
VENV_DIR="${BACKEND_DIR}/.venv"
ENV_FILE="${BACKEND_DIR}/.env"
DOMAIN="${DOMAIN:-finai.ctc-zc.com}"
DB_NAME="${DB_NAME:-finai}"
DB_USER="${DB_NAME}"
DB_PASS="${DB_PASS:-}"
FORCE_ENV="${FORCE_ENV:-0}"
SERVICE_NAME="finai-backend"
SERVICE_FILE="/etc/systemd/system/${SERVICE_NAME}.service"
# ─── 工具函数 ──────────────────────────────────────────────
c_green() { printf '\033[32m%s\033[0m\n' "$*"; }
c_yellow() { printf '\033[33m%s\033[0m\n' "$*"; }
c_red() { printf '\033[31m%s\033[0m\n' "$*" >&2; }
step() { printf '\n\033[36m▶ %s\033[0m\n' "$*"; }
require_cmd() {
command -v "$1" >/dev/null 2>&1 || {
c_red "缺少命令:$1,请先 apt 安装对应包"; exit 1;
}
}
# ─── 0. 前置检查 ───────────────────────────────────────────
step "0. 前置检查"
if [[ "${EUID}" -eq 0 ]]; then
c_red "请用 ubuntu 账号(非 root运行脚本内部会按需 sudo。"
exit 1
fi
if [[ "$(whoami)" != "ubuntu" ]]; then
c_yellow "当前用户是 $(whoami)(非 ubuntu若你确认要用当前用户可继续5 秒内 Ctrl+C 中止…"
sleep 5
fi
require_cmd python3.12
require_cmd psql
require_cmd systemctl
require_cmd openssl
require_cmd curl
if [[ ! -d "${BACKEND_DIR}" ]]; then
c_red "找不到 ${BACKEND_DIR},请先把代码上传到 ${FINAI_HOME}/backend"
exit 1
fi
if [[ ! -f "${BACKEND_DIR}/pyproject.toml" ]]; then
c_red "${BACKEND_DIR}/pyproject.toml 不存在,代码目录不完整"
exit 1
fi
sudo chown -R ubuntu:ubuntu "${FINAI_HOME}"
# ─── 1. Python 虚拟环境 + 依赖 ─────────────────────────────
step "1. 虚拟环境 & 依赖"
if [[ ! -x "${VENV_DIR}/bin/python" ]]; then
python3.12 -m venv "${VENV_DIR}"
c_green "venv 已创建:${VENV_DIR}"
else
c_yellow "venv 已存在,跳过创建"
fi
# shellcheck disable=SC1091
source "${VENV_DIR}/bin/activate"
pip install --upgrade pip >/dev/null
pip install . >/dev/null
c_green "依赖安装完成"
python - <<'PY'
import fastapi, sqlalchemy, pydantic_settings, httpx
from tencentcloud.lke.v20231130 import lke_client # noqa: F401
print(f" fastapi={fastapi.__version__} sqlalchemy={sqlalchemy.__version__}")
PY
mkdir -p "${STORAGE_DIR}"
# ─── 2. PostgreSQL 库 & 用户 ───────────────────────────────
step "2. PostgreSQL 建库建用户"
sudo systemctl is-active --quiet postgresql || {
c_red "postgresql 服务未运行,先 sudo systemctl start postgresql"
exit 1
}
user_exists=$(sudo -u postgres psql -tAc "SELECT 1 FROM pg_roles WHERE rolname='${DB_USER}'" || true)
db_exists=$(sudo -u postgres psql -tAc "SELECT 1 FROM pg_database WHERE datname='${DB_NAME}'" || true)
if [[ "${user_exists}" != "1" ]]; then
if [[ -z "${DB_PASS}" ]]; then
DB_PASS="$(openssl rand -hex 16)"
c_yellow "自动生成 DB_PASS = ${DB_PASS} ← 保存好"
fi
sudo -u postgres psql -v ON_ERROR_STOP=1 <<SQL
CREATE USER ${DB_USER} WITH PASSWORD '${DB_PASS}';
SQL
c_green "PG 用户 ${DB_USER} 已创建"
else
c_yellow "PG 用户 ${DB_USER} 已存在"
if [[ -z "${DB_PASS}" ]]; then
if [[ -f "${ENV_FILE}" ]] && grep -q '^DATABASE_URL=' "${ENV_FILE}"; then
DB_PASS="$(sed -nE 's|^DATABASE_URL=postgresql\+psycopg://[^:]+:([^@]+)@.*|\1|p' "${ENV_FILE}")"
c_yellow "从旧 .env 读回 DB_PASS = ${DB_PASS}"
else
DB_PASS="$(openssl rand -hex 16)"
c_yellow "旧用户密码未知,改写为新 DB_PASS = ${DB_PASS}"
sudo -u postgres psql -v ON_ERROR_STOP=1 <<SQL
ALTER USER ${DB_USER} WITH PASSWORD '${DB_PASS}';
SQL
fi
fi
fi
if [[ "${db_exists}" != "1" ]]; then
sudo -u postgres psql -v ON_ERROR_STOP=1 <<SQL
CREATE DATABASE ${DB_NAME} OWNER ${DB_USER} ENCODING 'UTF8';
GRANT ALL PRIVILEGES ON DATABASE ${DB_NAME} TO ${DB_USER};
SQL
c_green "PG 数据库 ${DB_NAME} 已创建"
else
c_yellow "PG 数据库 ${DB_NAME} 已存在"
fi
PGPASSWORD="${DB_PASS}" psql -h 127.0.0.1 -U "${DB_USER}" -d "${DB_NAME}" -c '\conninfo' \
|| { c_red "PG 连接失败,请检查 pg_hba.conf 是否放行 127.0.0.1/32 scram-sha-256"; exit 1; }
# ─── 3. 生成 .env ──────────────────────────────────────────
step "3. 生成 .env"
if [[ -f "${ENV_FILE}" && "${FORCE_ENV}" != "1" ]]; then
c_yellow ".env 已存在,保留不改。要重写请用 FORCE_ENV=1 重跑"
else
JWT_KEY="$(openssl rand -hex 32)"
cat > "${ENV_FILE}" <<EOF
ENVIRONMENT=production
DATABASE_URL=postgresql+psycopg://${DB_USER}:${DB_PASS}@127.0.0.1:5432/${DB_NAME}
JWT_SECRET_KEY=${JWT_KEY}
JWT_EXPIRE_MINUTES=480
ALLOWED_ORIGINS=https://${DOMAIN},http://${DOMAIN}
SMS_PROVIDER=mock
ADP_PROVIDER=mock
STORAGE_PROVIDER=local
LOCAL_STORAGE_DIR=${STORAGE_DIR}
# 接真实腾讯云 ADP 时把 mock 换 tencent并填以下
# ADP_BOT_APP_KEY=xxx
# ADP_KNOWLEDGE_BIZ_ID=xxx
# TENCENT_SECRET_ID=xxx
# TENCENT_SECRET_KEY=xxx
# TENCENT_REGION=ap-guangzhou
EOF
chmod 600 "${ENV_FILE}"
c_green ".env 已写入 ${ENV_FILE}"
fi
# ─── 4. systemd 单元 ──────────────────────────────────────
step "4. 部署 systemd 单元 ${SERVICE_NAME}"
sudo tee "${SERVICE_FILE}" >/dev/null <<EOF
[Unit]
Description=Finance AI Backend (FastAPI)
After=network.target postgresql.service redis-server.service
Wants=postgresql.service redis-server.service
[Service]
Type=simple
User=ubuntu
Group=ubuntu
WorkingDirectory=${BACKEND_DIR}
EnvironmentFile=${ENV_FILE}
ExecStart=${VENV_DIR}/bin/uvicorn app.main:app --host 127.0.0.1 --port 8000 --workers 2 --proxy-headers
Restart=always
RestartSec=3
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=full
ProtectHome=false
ReadWritePaths=${BACKEND_DIR}
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable "${SERVICE_NAME}" >/dev/null
sudo systemctl restart "${SERVICE_NAME}"
sleep 2
sudo systemctl --no-pager status "${SERVICE_NAME}" | head -n 12 || true
# ─── 5. 健康检查 ──────────────────────────────────────────
step "5. 健康检查"
for i in 1 2 3 4 5; do
if curl -fsS http://127.0.0.1:8000/health >/dev/null; then
c_green "GET /health OK"
break
fi
if [[ "$i" == "5" ]]; then
c_red "5 次探测均失败,跑 journalctl -u ${SERVICE_NAME} -n 100 --no-pager 排查"
exit 1
fi
sleep 2
done
# ─── 6. 总结 ──────────────────────────────────────────────
step "6. 完成 ✔"
cat <<EOF
后端已上线http://127.0.0.1:8000 (仅本机可达,供 Nginx 反代)
默认管理员: 手机号 13800000000 验证码 123456 mock 模式)
数据库: ${DB_NAME} / ${DB_USER} / ${DB_PASS}
JWT 密钥: 保存在 ${ENV_FILE}chmod 600
systemd sudo systemctl status ${SERVICE_NAME}
日志: journalctl -u ${SERVICE_NAME} -f
下一步:按 run.md §4 构建并发布前端到 NginxHTTPS 见 §4.4certbot -d ${DOMAIN})。
EOF