Files
secondary-school-grade-archive/deploy/install.sh
T
dekun e329d3398a Initial commit: secondary school grade archive system.
Add FastAPI/React app with Docker deployment, Ubuntu one-click install, and docs for junior/senior high score tracking and mistake bank.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-28 11:18:58 +08:00

232 lines
7.3 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 一键部署脚本
# 版权所有 (c) 马建军 微信: dekun03 手机: 18364911125
#
# 用法(root:
# curl -fsSL .../deploy/install.sh | bash
# 或
# bash deploy/install.sh
#
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}"
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 "无法识别操作系统,本脚本仅支持 Ubuntu/Debian 系 Linux"
exit 1
fi
# shellcheck source=/dev/null
source /etc/os-release
log_info "检测到系统: ${NAME:-Unknown} ${VERSION_ID:-}"
case "${ID:-}" in
ubuntu|debian)
;;
*)
log_warn "当前系统为 ${ID:-unknown},未经完整测试,继续安装…"
;;
esac
}
check_resources() {
local mem_kb disk_kb
mem_kb=$(grep MemTotal /proc/meminfo | awk '{print $2}')
disk_kb=$(df -k "${INSTALL_DIR%/*}" 2>/dev/null | tail -1 | awk '{print $4}')
if [[ "${mem_kb:-0}" -lt 1800000 ]]; then
log_warn "内存不足 2GBPaddleOCR 首次运行可能较慢"
fi
if [[ "${disk_kb:-0}" -lt 5242880 ]]; then
log_warn "可用磁盘空间不足 5GB,请确保有足够空间存放镜像与 OCR 模型"
fi
}
check_port() {
if command -v ss &>/dev/null; then
if ss -tln | grep -q ":${WEB_PORT} "; then
log_error "端口 ${WEB_PORT} 已被占用,请修改 WEB_PORT 环境变量后重试"
exit 1
fi
elif command -v netstat &>/dev/null; then
if netstat -tln | grep -q ":${WEB_PORT} "; then
log_error "端口 ${WEB_PORT} 已被占用"
exit 1
fi
fi
log_info "端口 ${WEB_PORT} 可用"
}
install_packages() {
log_info "安装基础依赖…"
apt-get update -qq
apt-get install -y -qq git curl ca-certificates openssl
}
install_docker() {
if command -v docker &>/dev/null; then
log_info "Docker 已安装: $(docker --version)"
else
log_info "正在安装 Docker…"
apt-get install -y -qq apt-transport-https gnupg lsb-release
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}") stable" \
> /etc/apt/sources.list.d/docker.list
apt-get update -qq
apt-get install -y -qq docker-ce docker-ce-cli containerd.io docker-compose-plugin
systemctl enable docker
systemctl start docker
log_info "Docker 安装完成"
fi
if ! docker compose version &>/dev/null; then
log_error "未检测到 docker compose 插件,请手动安装 docker-compose-plugin"
exit 1
fi
log_info "Docker Compose: $(docker compose version)"
}
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 --ff-only origin "${BRANCH}" || git -C "${INSTALL_DIR}" pull origin "${BRANCH}"
elif [[ -d "${INSTALL_DIR}" ]]; then
log_error "目录 ${INSTALL_DIR} 已存在但不是 git 仓库,请备份后删除或修改 INSTALL_DIR"
exit 1
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
}
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 配置"
# shellcheck source=/dev/null
source "${env_file}"
WEB_PORT="${WEB_PORT:-23566}"
return
fi
log_info "生成 .env 配置文件"
local secret pg_pass
secret=$(openssl rand -hex 32)
pg_pass=$(openssl rand -hex 16)
cat > "${env_file}" <<EOF
# 由 deploy/install.sh 自动生成 — $(date -Iseconds)
WEB_PORT=${WEB_PORT}
SECRET_KEY=${secret}
POSTGRES_USER=postgres
POSTGRES_PASSWORD=${pg_pass}
POSTGRES_DB=student_archive
CORS_ORIGINS=http://${server_ip}:${WEB_PORT},http://127.0.0.1:${WEB_PORT},http://localhost:${WEB_PORT}
OLLAMA_BASE_URL=http://host.docker.internal:11434
OLLAMA_MODEL=qwen2.5:7b
FLUCTUATION_THRESHOLD=0.08
EOF
chmod 600 "${env_file}"
log_info ".env 已写入 ${env_file}"
}
start_services() {
log_info "构建并启动 Docker 服务(首次可能需 1030 分钟)…"
cd "${INSTALL_DIR}"
mkdir -p uploads backups
docker compose --env-file .env pull 2>/dev/null || true
docker compose --env-file .env up -d --build
}
wait_healthy() {
local i max=60
log_info "等待 API 就绪…"
for ((i=1; i<=max; i++)); do
if docker compose --env-file "${INSTALL_DIR}/.env" exec -T api \
python -c "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/api/health')" &>/dev/null; then
log_info "API 健康检查通过"
return 0
fi
sleep 3
done
log_warn "API 启动超时,请执行: cd ${INSTALL_DIR} && docker compose logs api"
}
print_summary() {
local ip
ip=$(hostname -I 2>/dev/null | awk '{print $1}')
ip="${ip:-127.0.0.1}"
# shellcheck source=/dev/null
source "${INSTALL_DIR}/.env"
echo ""
echo "=========================================="
echo " 中学成绩档案系统 部署完成"
echo " 版权所有 (c) 马建军"
echo "=========================================="
echo ""
echo " 访问地址: http://${ip}:${WEB_PORT}"
echo " 本地访问: http://127.0.0.1:${WEB_PORT}"
echo " 安装目录: ${INSTALL_DIR}"
echo ""
echo " 常用命令:"
echo " 查看状态: cd ${INSTALL_DIR} && docker compose ps"
echo " 查看日志: cd ${INSTALL_DIR} && docker compose logs -f"
echo " 停止服务: cd ${INSTALL_DIR} && docker compose down"
echo " 更新版本: bash ${INSTALL_DIR}/deploy/update.sh"
echo " 数据备份: bash ${INSTALL_DIR}/deploy/backup.sh"
echo ""
echo " 反向代理(Nginx/Caddy 等)请自行配置,本项目不包含。"
echo " 详见文档: ${INSTALL_DIR}/docs/DEPLOY.md"
echo ""
echo " 技术支持: 微信 dekun03 手机 18364911125"
echo "=========================================="
}
main() {
echo ""
log_info "中学成绩档案系统 — 一键部署开始"
require_root
check_os
check_resources
check_port
install_packages
install_docker
clone_or_update_repo
generate_env
start_services
wait_healthy
print_summary
}
main "$@"