2a5fb5f469
Co-authored-by: Cursor <cursoragent@cursor.com>
249 lines
7.1 KiB
Bash
249 lines
7.1 KiB
Bash
#!/usr/bin/env bash
|
|
#
|
|
# 中学成绩档案系统 — Ubuntu PM2 一键部署
|
|
# 版权所有 (c) 马建军 微信: dekun03 手机: 18364911125
|
|
#
|
|
set -euo pipefail
|
|
|
|
REPO_URL="${REPO_URL:-https://git.bz121.com/dekun/secondary-school-grade-archive.git}"
|
|
INSTALL_DIR="${INSTALL_DIR:-/opt/secondary-school-grade-archive}"
|
|
WEB_PORT="${WEB_PORT:-23566}"
|
|
API_PORT="${API_PORT:-23568}"
|
|
BRANCH="${BRANCH:-main}"
|
|
NODE_MAJOR="${NODE_MAJOR:-20}"
|
|
PIP_MIRROR="${PIP_MIRROR:-https://pypi.tuna.tsinghua.edu.cn/simple}"
|
|
NPM_REGISTRY="${NPM_REGISTRY:-https://registry.npmmirror.com}"
|
|
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
NC='\033[0m'
|
|
|
|
log_info() { echo -e "${GREEN}[INFO]${NC} $*"; }
|
|
log_warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
|
|
log_error() { echo -e "${RED}[ERROR]${NC} $*" >&2; }
|
|
|
|
require_root() {
|
|
if [[ "${EUID:-$(id -u)}" -ne 0 ]]; then
|
|
log_error "请使用 root 用户运行: sudo bash deploy/install.sh"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
check_os() {
|
|
if [[ ! -f /etc/os-release ]]; then
|
|
log_error "无法识别操作系统"
|
|
exit 1
|
|
fi
|
|
# shellcheck source=/dev/null
|
|
source /etc/os-release
|
|
log_info "检测到系统: ${NAME:-Unknown} ${VERSION_ID:-}"
|
|
}
|
|
|
|
check_port() {
|
|
if command -v ss &>/dev/null && ss -tln | grep -q ":${WEB_PORT} "; then
|
|
log_error "端口 ${WEB_PORT} 已被占用"
|
|
exit 1
|
|
fi
|
|
log_info "端口 ${WEB_PORT} 可用"
|
|
}
|
|
|
|
install_base_packages() {
|
|
log_info "安装系统依赖…"
|
|
apt-get update -qq
|
|
DEBIAN_FRONTEND=noninteractive apt-get install -y -qq \
|
|
git curl ca-certificates openssl \
|
|
python3 python3-venv python3-pip python3-dev \
|
|
build-essential libpq-dev \
|
|
postgresql postgresql-contrib \
|
|
libgomp1 libglib2.0-0 libsm6 libxrender1 libxext6
|
|
}
|
|
|
|
install_node_pm2() {
|
|
if ! command -v node &>/dev/null || [[ "$(node -v | cut -d. -f1 | tr -d v)" -lt "${NODE_MAJOR}" ]]; then
|
|
log_info "安装 Node.js ${NODE_MAJOR}.x…"
|
|
curl -fsSL "https://deb.nodesource.com/setup_${NODE_MAJOR}.x" | bash -
|
|
apt-get install -y -qq nodejs
|
|
fi
|
|
log_info "Node: $(node -v) npm: $(npm -v)"
|
|
|
|
if ! command -v pm2 &>/dev/null; then
|
|
log_info "安装 PM2…"
|
|
npm install -g pm2
|
|
fi
|
|
log_info "PM2: $(pm2 -v)"
|
|
}
|
|
|
|
clone_or_update_repo() {
|
|
if [[ -d "${INSTALL_DIR}/.git" ]]; then
|
|
log_info "更新代码: ${INSTALL_DIR}"
|
|
git -C "${INSTALL_DIR}" fetch origin
|
|
git -C "${INSTALL_DIR}" checkout "${BRANCH}" 2>/dev/null || true
|
|
git -C "${INSTALL_DIR}" pull origin "${BRANCH}"
|
|
else
|
|
log_info "克隆仓库到 ${INSTALL_DIR}"
|
|
mkdir -p "$(dirname "${INSTALL_DIR}")"
|
|
git clone --branch "${BRANCH}" "${REPO_URL}" "${INSTALL_DIR}" || git clone "${REPO_URL}" "${INSTALL_DIR}"
|
|
fi
|
|
find "${INSTALL_DIR}" -name "*.sh" -exec sed -i 's/\r$//' {} +
|
|
}
|
|
|
|
generate_env() {
|
|
local env_file="${INSTALL_DIR}/.env"
|
|
local server_ip
|
|
server_ip=$(hostname -I 2>/dev/null | awk '{print $1}')
|
|
server_ip="${server_ip:-127.0.0.1}"
|
|
|
|
if [[ -f "${env_file}" ]]; then
|
|
log_info "保留已有 .env"
|
|
# shellcheck disable=SC1090
|
|
set -a && source "${env_file}" && set +a
|
|
return
|
|
fi
|
|
|
|
local secret pg_pass pg_user
|
|
secret=$(openssl rand -hex 32)
|
|
pg_pass=$(openssl rand -hex 16)
|
|
pg_user="gradeapp"
|
|
|
|
cat > "${env_file}" <<EOF
|
|
# generated by deploy/install.sh — $(date -Iseconds)
|
|
WEB_PORT=${WEB_PORT}
|
|
API_PORT=${API_PORT}
|
|
API_TARGET=http://127.0.0.1:${API_PORT}
|
|
SECRET_KEY=${secret}
|
|
POSTGRES_USER=${pg_user}
|
|
POSTGRES_PASSWORD=${pg_pass}
|
|
POSTGRES_DB=student_archive
|
|
DATABASE_URL=postgresql://${pg_user}:${pg_pass}@127.0.0.1:5432/student_archive
|
|
UPLOAD_DIR=${INSTALL_DIR}/uploads
|
|
CORS_ORIGINS=http://${server_ip}:${WEB_PORT},http://127.0.0.1:${WEB_PORT},http://localhost:${WEB_PORT}
|
|
OLLAMA_BASE_URL=http://127.0.0.1:11434
|
|
OLLAMA_MODEL=qwen2.5:7b
|
|
FLUCTUATION_THRESHOLD=0.08
|
|
EOF
|
|
chmod 600 "${env_file}"
|
|
log_info ".env 已生成"
|
|
}
|
|
|
|
setup_postgresql() {
|
|
# shellcheck disable=SC1090
|
|
source "${INSTALL_DIR}/.env"
|
|
log_info "配置 PostgreSQL…"
|
|
systemctl enable postgresql
|
|
systemctl start postgresql
|
|
|
|
if ! sudo -u postgres psql -tAc "SELECT 1 FROM pg_roles WHERE rolname='${POSTGRES_USER}'" | grep -q 1; then
|
|
sudo -u postgres psql -c "CREATE USER ${POSTGRES_USER} WITH PASSWORD '${POSTGRES_PASSWORD}';"
|
|
else
|
|
sudo -u postgres psql -c "ALTER USER ${POSTGRES_USER} WITH PASSWORD '${POSTGRES_PASSWORD}';"
|
|
fi
|
|
|
|
if ! sudo -u postgres psql -tAc "SELECT 1 FROM pg_database WHERE datname='${POSTGRES_DB}'" | grep -q 1; then
|
|
sudo -u postgres psql -c "CREATE DATABASE ${POSTGRES_DB} OWNER ${POSTGRES_USER};"
|
|
fi
|
|
sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE ${POSTGRES_DB} TO ${POSTGRES_USER};"
|
|
}
|
|
|
|
setup_backend() {
|
|
log_info "安装 Python 依赖(显示完整进度,Paddle 包较大,约 10–30 分钟)…"
|
|
cd "${INSTALL_DIR}/backend"
|
|
python3 -m venv venv
|
|
# shellcheck disable=SC1091
|
|
source venv/bin/activate
|
|
pip install --upgrade pip --progress-bar on -i "${PIP_MIRROR}"
|
|
pip install -r requirements.txt --progress-bar on -i "${PIP_MIRROR}"
|
|
deactivate
|
|
}
|
|
|
|
setup_frontend() {
|
|
log_info "构建前端…"
|
|
cd "${INSTALL_DIR}/frontend"
|
|
npm config set registry "${NPM_REGISTRY}"
|
|
npm ci
|
|
npm run build
|
|
}
|
|
|
|
setup_gateway() {
|
|
log_info "安装 Web 网关依赖…"
|
|
cd "${INSTALL_DIR}/deploy/pm2"
|
|
npm config set registry "${NPM_REGISTRY}"
|
|
npm ci
|
|
}
|
|
|
|
setup_pm2_startup() {
|
|
log_info "配置 PM2 开机自启…"
|
|
local startup_cmd
|
|
startup_cmd=$(pm2 startup systemd -u root --hp /root 2>&1 | grep -E '^sudo ' | tail -1 || true)
|
|
if [[ -n "${startup_cmd}" ]]; then
|
|
# shellcheck disable=SC2086
|
|
eval ${startup_cmd} || log_warn "PM2 开机自启配置失败,可稍后手动执行: pm2 startup"
|
|
else
|
|
log_warn "未获取 PM2 startup 命令,重启后需手动 pm2 resurrect"
|
|
fi
|
|
}
|
|
|
|
start_pm2() {
|
|
log_info "启动 PM2 服务…"
|
|
cd "${INSTALL_DIR}"
|
|
mkdir -p uploads backups
|
|
pm2 delete grade-api grade-web 2>/dev/null || true
|
|
pm2 start deploy/pm2/ecosystem.config.cjs
|
|
pm2 save
|
|
setup_pm2_startup
|
|
}
|
|
|
|
wait_healthy() {
|
|
local i
|
|
for i in $(seq 1 40); do
|
|
if curl -sf "http://127.0.0.1:${WEB_PORT}/api/health" >/dev/null; then
|
|
log_info "健康检查通过"
|
|
return 0
|
|
fi
|
|
sleep 3
|
|
done
|
|
log_warn "健康检查超时,请查看: pm2 logs"
|
|
}
|
|
|
|
print_summary() {
|
|
local ip
|
|
ip=$(hostname -I 2>/dev/null | awk '{print $1}')
|
|
ip="${ip:-127.0.0.1}"
|
|
echo ""
|
|
echo "=========================================="
|
|
echo " 中学成绩档案系统 PM2 部署完成"
|
|
echo " 版权所有 (c) 马建军"
|
|
echo "=========================================="
|
|
echo " 访问: http://${ip}:${WEB_PORT}"
|
|
echo " 目录: ${INSTALL_DIR}"
|
|
echo ""
|
|
echo " pm2 status"
|
|
echo " pm2 logs"
|
|
echo " bash ${INSTALL_DIR}/deploy/update.sh"
|
|
echo " bash ${INSTALL_DIR}/deploy/backup.sh"
|
|
echo ""
|
|
echo " 反向代理请自行配置,本项目不包含"
|
|
echo " 微信 dekun03 手机 18364911125"
|
|
echo "=========================================="
|
|
}
|
|
|
|
main() {
|
|
log_info "PM2 一键部署开始"
|
|
require_root
|
|
check_os
|
|
check_port
|
|
install_base_packages
|
|
install_node_pm2
|
|
clone_or_update_repo
|
|
generate_env
|
|
setup_postgresql
|
|
setup_backend
|
|
setup_frontend
|
|
setup_gateway
|
|
start_pm2
|
|
wait_healthy
|
|
print_summary
|
|
}
|
|
|
|
main "$@"
|