#!/usr/bin/env bash # # 中学成绩档案系统 — 一键部署 # 架构:主程序 + OCR(GPU/screen 同机) | Ollama(其他电脑局域网) # 版权所有 (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}" OCR_PORT="${OCR_PORT:-23567}" BRANCH="${BRANCH:-main}" PIP_MIRROR="${PIP_MIRROR:-https://pypi.tuna.tsinghua.edu.cn/simple}" # Ollama 在其他电脑时安装前指定,例如: OLLAMA_BASE_URL=http://192.168.8.100:11434 OLLAMA_BASE_URL="${OLLAMA_BASE_URL:-}" OLLAMA_MODEL="${OLLAMA_MODEL:-qwen2.5:7b}" 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; } # shellcheck source=proxy.sh source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/proxy.sh" # shellcheck source=ocr-common.sh source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/ocr-common.sh" 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() { for p in "${WEB_PORT}" "${OCR_PORT}"; do if command -v ss &>/dev/null && ss -tln | grep -q ":${p} "; then if [[ "${p}" == "${OCR_PORT}" ]] && ocr_screen_running 2>/dev/null; then log_warn "端口 ${OCR_PORT} 已被占用(可能已有 OCR Worker)" elif [[ "${p}" == "${WEB_PORT}" ]]; then log_error "端口 ${WEB_PORT} 已被占用" exit 1 fi fi done log_info "Web 端口 ${WEB_PORT} / OCR 端口 ${OCR_PORT} 检查完成" } prompt_ollama_url() { if [[ -n "${OLLAMA_BASE_URL}" ]]; then log_info "Ollama 地址: ${OLLAMA_BASE_URL}" return fi if [[ -t 0 ]]; then echo "" echo "Ollama 部署在【其他电脑】上,请填写局域网地址。" read -rp "Ollama 地址 [http://192.168.8.100:11434]: " input OLLAMA_BASE_URL="${input:-http://192.168.8.100:11434}" else OLLAMA_BASE_URL="http://127.0.0.1:11434" log_warn "未设置 OLLAMA_BASE_URL,默认 ${OLLAMA_BASE_URL}(可在 .env 或系统设置中修改)" fi log_info "Ollama 地址: ${OLLAMA_BASE_URL}" } install_base_packages() { log_info "安装系统依赖…" apt-get update -qq DEBIAN_FRONTEND=noninteractive apt-get install -y -qq \ git curl ca-certificates openssl screen \ python3 python3-venv python3-pip python3-dev \ build-essential libpq-dev \ postgresql postgresql-contrib \ libgl1 libglx-mesa0 libgbm1 \ libgomp1 libglib2.0-0 libsm6 libxrender1 libxext6 libxcb1 libfontconfig1 } 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" \ "${INSTALL_DIR}/deploy/ocr-screen.sh" \ "${INSTALL_DIR}/deploy/ocr-worker/"*.sh 2>/dev/null || true } verify_frontend_dist() { if [[ ! -f "${INSTALL_DIR}/frontend/dist/index.html" ]]; then log_error "未找到 frontend/dist/index.html" log_error "仓库应已包含预构建前端;若缺失请在开发机 npm run build 后推送" exit 1 fi log_info "前端静态资源已就绪" } env_set_or_append() { local key="$1" val="$2" file="${INSTALL_DIR}/.env" if grep -q "^${key}=" "${file}" 2>/dev/null; then sed -i "s|^${key}=.*|${key}=${val}|" "${file}" else echo "${key}=${val}" >> "${file}" fi } 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}" prompt_ollama_url if [[ ! -f "${env_file}" ]]; then 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 log_info "检测到 NVIDIA GPU,OCR 将常驻显存 (screen)" nvidia-smi --query-gpu=name,memory.total --format=csv,noheader 2>/dev/null || true else log_warn "未检测到 NVIDIA GPU,OCR 将使用 CPU(较慢)" fi install_ocr_worker start_ocr_screen wait_ocr_healthy || log_warn "OCR 仍在加载模型,稍后可执行: bash ${INSTALL_DIR}/deploy/ocr-screen.sh status" } stop_legacy_pm2() { if command -v pm2 &>/dev/null; then pm2 delete grade-api grade-web 2>/dev/null || true pm2 save 2>/dev/null || true log_info "已停止旧版 PM2 进程" fi } setup_systemd() { log_info "配置 systemd 服务 grade-archive(主程序)…" 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() { # shellcheck disable=SC1090 source "${INSTALL_DIR}/.env" local ip ip=$(hostname -I 2>/dev/null | awk '{print $1}') ip="${ip:-127.0.0.1}" echo "" echo "==========================================" echo " 中学成绩档案 — 一键部署完成" echo " 版权所有 (c) 马建军" echo "==========================================" echo " 访问: http://${ip}:${WEB_PORT}" echo " 管理员: admin / admin123(请立即修改)" echo "" echo " 【同机 OCR — GPU 常驻 screen】" echo " OCR 地址: http://127.0.0.1:${OCR_PORT}" echo " 状态: bash ${INSTALL_DIR}/deploy/ocr-screen.sh status" echo " 进入终端: screen -r ocr-worker (Ctrl+A D 退出)" echo " 重启 OCR: bash ${INSTALL_DIR}/deploy/ocr-screen.sh restart" echo "" echo " 【Ollama — 其他电脑】" echo " 地址: ${OLLAMA_BASE_URL}" echo " 模型: ${OLLAMA_MODEL}" echo " 可在「系统设置 → AI 模型」修改" echo "" echo " 主程序: systemctl status grade-archive" echo " 更新: sudo bash ${INSTALL_DIR}/deploy/update.sh" echo " 微信 dekun03 手机 18364911125" echo "==========================================" } main() { log_info "一键部署开始(主程序 + OCR/GPU/screen | Ollama 外置)" require_root setup_deploy_proxy check_os clone_or_update_repo verify_frontend_dist check_port install_base_packages generate_env setup_postgresql setup_backend setup_ocr_gpu stop_legacy_pm2 setup_systemd start_service wait_healthy print_summary } main "$@"