#!/usr/bin/env bash # # 中学成绩档案系统 — Ubuntu 零 Node 一键部署(FastAPI 单进程 + systemd) # 版权所有 (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}" BRANCH="${BRANCH:-main}" PIP_MIRROR="${PIP_MIRROR:-https://pypi.tuna.tsinghua.edu.cn/simple}" 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 } 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$//' {} + chmod +x "${INSTALL_DIR}/deploy/start.sh" } verify_frontend_dist() { if [[ ! -f "${INSTALL_DIR}/frontend/dist/index.html" ]]; then log_error "未找到 frontend/dist/index.html" log_error "请先在开发机执行: cd frontend && npm run build,并将 dist 推送到仓库后再部署" exit 1 fi log_info "前端静态资源已就绪(无需在服务器构建)" } 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" 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}" </dev/null; then pm2 delete grade-api grade-web 2>/dev/null || true pm2 save 2>/dev/null || true log_info "已停止旧版 PM2 进程(grade-api / grade-web)" fi } setup_systemd() { log_info "配置 systemd 服务…" sed "s|/opt/secondary-school-grade-archive|${INSTALL_DIR}|g" \ "${INSTALL_DIR}/deploy/grade-archive.service" > /etc/systemd/system/grade-archive.service systemctl daemon-reload systemctl enable grade-archive } start_service() { log_info "启动服务…" cd "${INSTALL_DIR}" mkdir -p uploads backups systemctl restart grade-archive } 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 "健康检查超时,请查看: journalctl -u grade-archive -f" } print_summary() { local ip ip=$(hostname -I 2>/dev/null | awk '{print $1}') ip="${ip:-127.0.0.1}" echo "" echo "==========================================" echo " 中学成绩档案系统部署完成(零 Node)" echo " 版权所有 (c) 马建军" echo "==========================================" echo " 访问: http://${ip}:${WEB_PORT}" echo " 目录: ${INSTALL_DIR}" echo " 默认管理员: admin / admin123(请立即修改)" echo "" echo " systemctl status grade-archive" echo " journalctl -u grade-archive -f" echo " bash ${INSTALL_DIR}/deploy/update.sh" echo " bash ${INSTALL_DIR}/deploy/backup.sh" echo "" echo " 微信 dekun03 手机 18364911125" echo "==========================================" } main() { log_info "零 Node 一键部署开始" require_root check_os check_port install_base_packages clone_or_update_repo verify_frontend_dist generate_env setup_postgresql setup_backend stop_legacy_pm2 setup_systemd start_service wait_healthy print_summary } main "$@"