零 Node 部署、超级管理员,并完善本地构建发布文档。
- FastAPI 单进程托管 frontend/dist,systemd 替代 PM2 - 超级管理员 admin、注册开关与用户管理 - README/DEPLOY/USAGE 说明:改代码须本地构建 dist 后 push,服务器 update.sh - 提交 frontend/dist 与 build-frontend 脚本
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
# 本地构建前端并提示提交 dist(开发机执行,服务器不构建)
|
||||
$ErrorActionPreference = "Stop"
|
||||
$Root = Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path)
|
||||
Set-Location (Join-Path $Root "frontend")
|
||||
|
||||
Write-Host "[INFO] 安装依赖并构建前端…"
|
||||
npm ci
|
||||
npm run build
|
||||
|
||||
if (-not (Test-Path "dist\index.html")) {
|
||||
Write-Error "构建失败:未生成 dist\index.html"
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "构建完成。请提交并推送:"
|
||||
Write-Host " git add frontend/dist"
|
||||
Write-Host ' git commit -m "build: update frontend dist"'
|
||||
Write-Host " git push"
|
||||
Write-Host ""
|
||||
Write-Host "服务器更新:"
|
||||
Write-Host " bash /opt/secondary-school-grade-archive/deploy/update.sh"
|
||||
@@ -0,0 +1,24 @@
|
||||
#!/usr/bin/env bash
|
||||
# 本地构建前端并提示提交 dist(开发机执行,服务器不构建)
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
cd "${ROOT}/frontend"
|
||||
|
||||
echo "[INFO] 安装依赖并构建前端…"
|
||||
npm ci
|
||||
npm run build
|
||||
|
||||
if [[ ! -f dist/index.html ]]; then
|
||||
echo "[ERROR] 构建失败:未生成 dist/index.html"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "构建完成。请提交并推送:"
|
||||
echo " git add frontend/dist"
|
||||
echo " git commit -m \"build: update frontend dist\""
|
||||
echo " git push"
|
||||
echo ""
|
||||
echo "服务器更新:"
|
||||
echo " bash /opt/secondary-school-grade-archive/deploy/update.sh"
|
||||
@@ -0,0 +1,15 @@
|
||||
[Unit]
|
||||
Description=Secondary School Grade Archive
|
||||
After=network.target postgresql.service
|
||||
Wants=postgresql.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
WorkingDirectory=/opt/secondary-school-grade-archive
|
||||
ExecStart=/opt/secondary-school-grade-archive/deploy/start.sh
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
EnvironmentFile=-/opt/secondary-school-grade-archive/.env
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
+42
-65
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# 中学成绩档案系统 — Ubuntu PM2 一键部署
|
||||
# 中学成绩档案系统 — Ubuntu 零 Node 一键部署(FastAPI 单进程 + systemd)
|
||||
# 版权所有 (c) 马建军 微信: dekun03 手机: 18364911125
|
||||
#
|
||||
set -euo pipefail
|
||||
@@ -8,11 +8,8 @@ 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:-23568}"
|
||||
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'
|
||||
@@ -59,21 +56,6 @@ install_base_packages() {
|
||||
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}"
|
||||
@@ -86,6 +68,16 @@ clone_or_update_repo() {
|
||||
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() {
|
||||
@@ -96,8 +88,6 @@ generate_env() {
|
||||
|
||||
if [[ -f "${env_file}" ]]; then
|
||||
log_info "保留已有 .env"
|
||||
# shellcheck disable=SC1090
|
||||
set -a && source "${env_file}" && set +a
|
||||
return
|
||||
fi
|
||||
|
||||
@@ -109,8 +99,7 @@ generate_env() {
|
||||
cat > "${env_file}" <<EOF
|
||||
# generated by deploy/install.sh — $(date -Iseconds)
|
||||
WEB_PORT=${WEB_PORT}
|
||||
API_PORT=${API_PORT}
|
||||
API_TARGET=http://127.0.0.1:${API_PORT}
|
||||
FRONTEND_DIST=${INSTALL_DIR}/frontend/dist
|
||||
SECRET_KEY=${secret}
|
||||
POSTGRES_USER=${pg_user}
|
||||
POSTGRES_PASSWORD=${pg_pass}
|
||||
@@ -121,9 +110,11 @@ CORS_ORIGINS=http://${server_ip}:${WEB_PORT},http://127.0.0.1:${WEB_PORT},http:/
|
||||
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 已生成"
|
||||
log_info ".env 已生成(默认超级管理员 admin / admin123,请登录后修改)"
|
||||
}
|
||||
|
||||
setup_postgresql() {
|
||||
@@ -146,7 +137,7 @@ setup_postgresql() {
|
||||
}
|
||||
|
||||
setup_backend() {
|
||||
log_info "安装 Python 依赖(显示完整进度,Paddle 包较大,约 10–30 分钟)…"
|
||||
log_info "安装 Python 依赖(Paddle 包较大,约 10–30 分钟)…"
|
||||
cd "${INSTALL_DIR}/backend"
|
||||
python3 -m venv venv
|
||||
# shellcheck disable=SC1091
|
||||
@@ -156,41 +147,27 @@ setup_backend() {
|
||||
deactivate
|
||||
}
|
||||
|
||||
setup_frontend() {
|
||||
log_info "构建前端…"
|
||||
cd "${INSTALL_DIR}/frontend"
|
||||
npm config set registry "${NPM_REGISTRY}"
|
||||
npm ci
|
||||
npm run build
|
||||
}
|
||||
|
||||
setup_gateway() {
|
||||
log_info "安装 Web 网关依赖…"
|
||||
cd "${INSTALL_DIR}/deploy/pm2"
|
||||
npm config set registry "${NPM_REGISTRY}"
|
||||
npm ci
|
||||
}
|
||||
|
||||
setup_pm2_startup() {
|
||||
log_info "配置 PM2 开机自启…"
|
||||
local startup_cmd
|
||||
startup_cmd=$(pm2 startup systemd -u root --hp /root 2>&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"
|
||||
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
|
||||
}
|
||||
|
||||
start_pm2() {
|
||||
log_info "启动 PM2 服务…"
|
||||
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
|
||||
pm2 delete grade-api grade-web 2>/dev/null || true
|
||||
pm2 start deploy/pm2/ecosystem.config.cjs
|
||||
pm2 save
|
||||
setup_pm2_startup
|
||||
systemctl restart grade-archive
|
||||
}
|
||||
|
||||
wait_healthy() {
|
||||
@@ -202,7 +179,7 @@ wait_healthy() {
|
||||
fi
|
||||
sleep 3
|
||||
done
|
||||
log_warn "健康检查超时,请查看: pm2 logs"
|
||||
log_warn "健康检查超时,请查看: journalctl -u grade-archive -f"
|
||||
}
|
||||
|
||||
print_summary() {
|
||||
@@ -211,36 +188,36 @@ print_summary() {
|
||||
ip="${ip:-127.0.0.1}"
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo " 中学成绩档案系统 PM2 部署完成"
|
||||
echo " 中学成绩档案系统部署完成(零 Node)"
|
||||
echo " 版权所有 (c) 马建军"
|
||||
echo "=========================================="
|
||||
echo " 访问: http://${ip}:${WEB_PORT}"
|
||||
echo " 目录: ${INSTALL_DIR}"
|
||||
echo " 默认管理员: admin / admin123(请立即修改)"
|
||||
echo ""
|
||||
echo " pm2 status"
|
||||
echo " pm2 logs"
|
||||
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 " 反向代理请自行配置,本项目不包含"
|
||||
echo " 微信 dekun03 手机 18364911125"
|
||||
echo "=========================================="
|
||||
}
|
||||
|
||||
main() {
|
||||
log_info "PM2 一键部署开始"
|
||||
log_info "零 Node 一键部署开始"
|
||||
require_root
|
||||
check_os
|
||||
check_port
|
||||
install_base_packages
|
||||
install_node_pm2
|
||||
clone_or_update_repo
|
||||
verify_frontend_dist
|
||||
generate_env
|
||||
setup_postgresql
|
||||
setup_backend
|
||||
setup_frontend
|
||||
setup_gateway
|
||||
start_pm2
|
||||
stop_legacy_pm2
|
||||
setup_systemd
|
||||
start_service
|
||||
wait_healthy
|
||||
print_summary
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
cd "${ROOT}/backend"
|
||||
|
||||
# shellcheck disable=SC1091
|
||||
source venv/bin/activate
|
||||
|
||||
if [[ -f "${ROOT}/.env" ]]; then
|
||||
set -a
|
||||
# shellcheck disable=SC1090
|
||||
source "${ROOT}/.env"
|
||||
set +a
|
||||
fi
|
||||
|
||||
export FRONTEND_DIST="${FRONTEND_DIST:-${ROOT}/frontend/dist}"
|
||||
export UPLOAD_DIR="${UPLOAD_DIR:-${ROOT}/uploads}"
|
||||
|
||||
exec python -m uvicorn app.main:app --host 0.0.0.0 --port "${WEB_PORT:-23566}"
|
||||
+15
-15
@@ -4,39 +4,39 @@ set -euo pipefail
|
||||
INSTALL_DIR="${INSTALL_DIR:-/opt/secondary-school-grade-archive}"
|
||||
BRANCH="${BRANCH:-main}"
|
||||
PIP_MIRROR="${PIP_MIRROR:-https://pypi.tuna.tsinghua.edu.cn/simple}"
|
||||
NPM_REGISTRY="${NPM_REGISTRY:-https://registry.npmmirror.com}"
|
||||
|
||||
GREEN='\033[0;32m'
|
||||
RED='\033[0;31m'
|
||||
NC='\033[0m'
|
||||
log_info() { echo -e "${GREEN}[INFO]${NC} $*"; }
|
||||
log_error() { echo -e "${RED}[ERROR]${NC} $*" >&2; }
|
||||
|
||||
cd "${INSTALL_DIR}" || exit 1
|
||||
find "${INSTALL_DIR}" -name "*.sh" -exec sed -i 's/\r$//' {} +
|
||||
chmod +x deploy/start.sh
|
||||
|
||||
log_info "拉取最新代码…"
|
||||
git fetch origin
|
||||
git checkout "${BRANCH}" 2>/dev/null || true
|
||||
git pull origin "${BRANCH}"
|
||||
|
||||
if [[ ! -f "${INSTALL_DIR}/frontend/dist/index.html" ]]; then
|
||||
log_error "未找到 frontend/dist/index.html"
|
||||
log_error "请先在开发机构建前端并推送到仓库: cd frontend && npm run build && git add frontend/dist && git push"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_info "更新后端依赖…"
|
||||
cd backend
|
||||
source venv/bin/activate
|
||||
pip install -r requirements.txt --progress-bar on -i "${PIP_MIRROR}"
|
||||
deactivate
|
||||
|
||||
log_info "重建前端…"
|
||||
cd ../frontend
|
||||
npm config set registry "${NPM_REGISTRY}"
|
||||
npm ci
|
||||
npm run build
|
||||
if command -v pm2 &>/dev/null; then
|
||||
pm2 delete grade-api grade-web 2>/dev/null || true
|
||||
fi
|
||||
|
||||
log_info "更新网关…"
|
||||
cd ../deploy/pm2
|
||||
npm ci
|
||||
log_info "重启 systemd 服务…"
|
||||
systemctl restart grade-archive
|
||||
|
||||
log_info "重启 PM2…"
|
||||
cd "${INSTALL_DIR}"
|
||||
pm2 reload deploy/pm2/ecosystem.config.cjs --update-env || pm2 start deploy/pm2/ecosystem.config.cjs
|
||||
pm2 save
|
||||
|
||||
log_info "更新完成"
|
||||
log_info "更新完成 — 访问端口见 .env 中 WEB_PORT(默认 23566)"
|
||||
|
||||
Reference in New Issue
Block a user