#!/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:-8000}" 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}" <&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 "$@"