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

230 lines
6.8 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
#
# 中学成绩档案系统 — 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; }
# shellcheck source=proxy.sh
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/proxy.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() {
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}" <<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}
OLLAMA_BASE_URL=http://127.0.0.1:11434
OLLAMA_MODEL=qwen2.5:7b
FLUCTUATION_THRESHOLD=0.08
ADMIN_DEFAULT_USERNAME=admin
ADMIN_DEFAULT_PASSWORD=admin123
EOF
chmod 600 "${env_file}"
log_info ".env 已生成(默认超级管理员 admin / admin123,请登录后修改)"
}
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 包较大,约 1030 分钟)…"
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
}
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 进程(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
setup_deploy_proxy
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 "$@"