#!/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 "内存不足 2GB,PaddleOCR 首次运行可能较慢" 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}" </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 "$@"