Files
secondary-school-grade-archive/deploy/install.sh
T

311 lines
9.9 KiB
Bash
Raw 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
#
# 中学成绩档案系统 — 一键部署
# 架构:主程序 + 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}" <<EOF
# generated by deploy/install.sh — $(date -Iseconds)
WEB_PORT=${WEB_PORT}
FRONTEND_DIST=${INSTALL_DIR}/frontend/dist
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}
# OCR 同机 GPU Workerscreen 常驻)
OCR_SERVICE_URL=http://127.0.0.1:${OCR_PORT}
OCR_PORT=${OCR_PORT}
# Ollama 在其他电脑(局域网)
OLLAMA_BASE_URL=${OLLAMA_BASE_URL}
OLLAMA_MODEL=${OLLAMA_MODEL}
FLUCTUATION_THRESHOLD=0.08
ADMIN_DEFAULT_USERNAME=admin
ADMIN_DEFAULT_PASSWORD=admin123
EOF
chmod 600 "${env_file}"
log_info ".env 已生成(默认 admin / admin123"
else
log_info "更新 .env 中的 OCR / Ollama 配置…"
env_set_or_append "OCR_SERVICE_URL" "http://127.0.0.1:${OCR_PORT}"
env_set_or_append "OCR_PORT" "${OCR_PORT}"
env_set_or_append "OLLAMA_BASE_URL" "${OLLAMA_BASE_URL}"
env_set_or_append "OLLAMA_MODEL" "${OLLAMA_MODEL}"
fi
}
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 依赖…"
cd "${INSTALL_DIR}/backend"
if [[ ! -d venv ]]; then
python3 -m venv venv
fi
# 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_ocr_gpu() {
if command -v nvidia-smi >/dev/null; then
log_info "检测到 NVIDIA GPUOCR 将常驻显存 (screen)"
nvidia-smi --query-gpu=name,memory.total --format=csv,noheader 2>/dev/null || true
else
log_warn "未检测到 NVIDIA GPUOCR 将使用 CPU(较慢)"
fi
install_ocr_worker
start_ocr_screen
wait_ocr_healthy 30 || log_warn "OCR 后台加载中,继续安装主程序…"
}
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
log_info "等待主程序就绪(最多 2 分钟)…"
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
if (( i % 5 == 0 )); then
echo -ne "\r${YELLOW}[INFO]${NC} 等待主程序… ${i}/40"
fi
sleep 3
done
echo ""
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 " 卸载: sudo bash ${INSTALL_DIR}/deploy/uninstall.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 "$@"