#!/usr/bin/env bash # ═══════════════════════════════════════════════════════════ # FutureOSS 智能启动脚本 - Linux # 自动检测环境 / 安装依赖 / 进度显示 / 守护重启 # ═══════════════════════════════════════════════════════════ PROJECT_DIR="$(cd "$(dirname "$0")" && pwd)" cd "$PROJECT_DIR" # ── 颜色 ── RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' CYAN='\033[0;36m' BLUE='\033[1;34m' WHITE='\033[1;37m' BOLD='\033[1m' NC='\033[0m' # ── 工具函数 ── info() { echo -e "${CYAN}[信息]${NC} $1"; } ok() { echo -e "${GREEN}[成功]${NC} $1"; } warn() { echo -e "${YELLOW}[警告]${NC} $1"; } err() { echo -e "${RED}[错误]${NC} $1"; } step() { echo -e "\n${BOLD}${BLUE}▶ $1${NC}"; } # 进度条 progress_bar() { local current=$1 local total=$2 local label=$3 local pct=$((current * 100 / total)) local filled=$((pct / 2)) local empty=$((50 - filled)) local bar="" for ((i=0; i/dev/null; then PKG_MANAGER="apt" ok "包管理器: apt (Debian/Ubuntu)" elif command -v yum &>/dev/null; then PKG_MANAGER="yum" ok "包管理器: yum (CentOS/RHEL)" elif command -v dnf &>/dev/null; then PKG_MANAGER="dnf" ok "包管理器: dnf (Fedora)" elif command -v pacman &>/dev/null; then PKG_MANAGER="pacman" ok "包管理器: pacman (Arch Linux)" elif command -v apk &>/dev/null; then PKG_MANAGER="apk" ok "包管理器: apk (Alpine)" else warn "未检测到已知包管理器,将尝试使用系统自带工具" fi install_pkg() { local pkg=$1 case $PKG_MANAGER in apt) sudo apt-get install -y -qq "$pkg" 2>/dev/null ;; yum) sudo yum install -y -q "$pkg" 2>/dev/null ;; dnf) sudo dnf install -y -q "$pkg" 2>/dev/null ;; pacman) sudo pacman -S --noconfirm "$pkg" 2>/dev/null ;; apk) sudo apk add --quiet "$pkg" 2>/dev/null ;; *) warn "无法自动安装 $pkg" ; return 1 ;; esac } update_pkg_cache() { case $PKG_MANAGER in apt) sudo apt-get update -qq 2>/dev/null ;; yum) sudo yum makecache -q 2>/dev/null ;; dnf) sudo dnf makecache -q 2>/dev/null ;; pacman) sudo pacman -Sy --quiet 2>/dev/null ;; apk) sudo apk update --quiet 2>/dev/null ;; esac } TOTAL_STEPS=6 CURRENT_STEP=0 # ═══════════════════════════════════════════════════════════ # 2. 安装系统依赖 # ═══════════════════════════════════════════════════════════ step "安装系统依赖" # 系统命令依赖和PHP扩展分开处理 CMD_DEPENDENCIES=("git" "curl" "wget" "php" "python3") PHP_EXTENSIONS=("php-cli" "php-mbstring" "php-xml" "php-zip") PYTHON_PKGS=("pip" "venv") DEPENDENCIES=(${CMD_DEPENDENCIES[@]} ${PHP_EXTENSIONS[@]} "python3-pip" "python3-venv") DEP_TOTAL=${#DEPENDENCIES[@]} DEP_INSTALLED=0 for dep in "${DEPENDENCIES[@]}"; do DEP_INSTALLED=$((DEP_INSTALLED + 1)) progress_bar $DEP_INSTALLED $DEP_TOTAL "检测 $dep" # PHP扩展包不通过command检测,跳过 if [[ "$dep" == php-* ]]; then continue fi # python3-venv也不通过command检测 if [[ "$dep" == "python3-venv" || "$dep" == "python3-pip" ]]; then continue fi if command -v "$dep" &>/dev/null; then continue fi # 尝试安装 if ! install_pkg "$dep" 2>/dev/null; then # 更新缓存后重试 update_pkg_cache 2>/dev/null install_pkg "$dep" 2>/dev/null || true fi if command -v "$dep" &>/dev/null; then continue fi done echo -e "\n" # 验证关键依赖 if command -v php &>/dev/null; then ok "PHP: $(php --version 2>&1 | head -n 1)" else warn "PHP 未安装,WebUI 可能无法正常工作" fi if command -v python3 &>/dev/null; then ok "Python: $(python3 --version 2>&1)" else err "Python3 未安装,无法继续" exit 1 fi # ═══════════════════════════════════════════════════════════ # 3. Python 虚拟环境 # ═══════════════════════════════════════════════════════════ step "配置 Python 环境" PYTHON_CMD="python3" VENV_DIR=".venv" if [[ ! -d "$VENV_DIR" ]]; then info "创建虚拟环境..." $PYTHON_CMD -m venv "$VENV_DIR" 2>/dev/null || { warn "venv 模块缺失,尝试安装..." case $PKG_MANAGER in apt) install_pkg "python3-venv" ;; yum) install_pkg "python3-virtualenv" ;; dnf) install_pkg "python3-virtualenv" ;; pacman) ;; apk) install_pkg "py3-virtualenv" ;; esac $PYTHON_CMD -m venv "$VENV_DIR" 2>/dev/null || { err "无法创建虚拟环境" exit 1 } } ok "虚拟环境已创建" else ok "虚拟环境已存在" fi source "$VENV_DIR/bin/activate" PIP_CMD="$VENV_DIR/bin/pip" # ═══════════════════════════════════════════════════════════ # 检测虚拟环境依赖完整性 # ═══════════════════════════════════════════════════════════ info "检测虚拟环境依赖完整性..." VENV_INCOMPLETE=false MISSING_VENV_DEPS=() for pkg in "${CORE_DEPS[@]}"; do import_name="${PKG_IMPORT_MAP[$pkg]}" if ! $PYTHON_CMD -c "import $import_name" 2>/dev/null; then VENV_INCOMPLETE=true MISSING_VENV_DEPS+=("$pkg") fi done # 同时检测 requirements.txt 中的依赖 if [[ -f "requirements.txt" ]]; then while IFS= read -r line || [[ -n "$line" ]]; do # 跳过空行和注释 [[ -z "$line" || "$line" =~ ^[[:space:]]*# ]] && continue # 提取包名(去除版本号) pkg_name=$(echo "$line" | sed -E 's/([a-zA-Z0-9_-]+).*/\1/') [[ -z "$pkg_name" ]] && continue # 尝试导入(将连字符替换为下划线) import_name="${pkg_name//-/_}" if ! $PYTHON_CMD -c "import $import_name" 2>/dev/null; then # 不在核心依赖列表中的才添加 if [[ ! " ${CORE_DEPS[@]} " =~ " ${pkg_name} " ]]; then VENV_INCOMPLETE=true MISSING_VENV_DEPS+=("$pkg_name") fi fi done < "requirements.txt" fi if $VENV_INCOMPLETE; then warn "虚拟环境依赖不完整,缺失: ${MISSING_VENV_DEPS[*]}" info "正在安装缺失的依赖..." for pkg in "${MISSING_VENV_DEPS[@]}"; do $PIP_CMD install "$pkg" -q 2>/dev/null || \ $PIP_CMD install "$pkg" --break-system-packages -q 2>/dev/null || \ warn "无法安装 $pkg" done else ok "虚拟环境依赖完整" fi # ═══════════════════════════════════════════════════════════ # 4. 安装 Python 依赖 # ═══════════════════════════════════════════════════════════ step "安装 Python 依赖" # 包名到导入名的映射 declare -A PKG_IMPORT_MAP=( ["click"]="click" ["pyyaml"]="yaml" ["websockets"]="websockets" ["psutil"]="psutil" ["cryptography"]="cryptography" ) CORE_DEPS=("click" "pyyaml" "websockets" "psutil" "cryptography") DEP_COUNT=${#CORE_DEPS[@]} DEP_CURRENT=0 for pkg in "${CORE_DEPS[@]}"; do DEP_CURRENT=$((DEP_CURRENT + 1)) progress_bar $DEP_CURRENT $DEP_COUNT "安装 $pkg" # 使用正确的导入名检测 import_name="${PKG_IMPORT_MAP[$pkg]}" $PYTHON_CMD -c "import $import_name" 2>/dev/null && continue $PIP_CMD install "$pkg" -q 2>/dev/null || \ $PIP_CMD install "$pkg" --break-system-packages -q 2>/dev/null || true done echo -e "\n" # 安装项目依赖 if [[ -f "pyproject.toml" ]]; then info "安装项目配置依赖..." $PIP_CMD install -e . -q 2>/dev/null || true fi if [[ -f "requirements.txt" ]]; then info "安装 requirements.txt..." $PIP_CMD install -r requirements.txt -q 2>/dev/null || true fi ok "Python 依赖安装完成" # ═══════════════════════════════════════════════════════════ # 5. 创建数据目录 # ═══════════════════════════════════════════════════════════ step "初始化数据目录" DATA_DIRS=("data" "data/html-render" "data/web-toolkit" "data/plugin-storage" "data/DCIM" "data/signature-verifier/keys/private" "data/signature-verifier/keys/public" "logs") DIR_COUNT=${#DATA_DIRS[@]} DIR_CURRENT=0 for dir in "${DATA_DIRS[@]}"; do DIR_CURRENT=$((DIR_CURRENT + 1)) progress_bar $DIR_CURRENT $DIR_COUNT "创建 $dir" mkdir -p "$dir" done echo -e "\n" ok "数据目录已就绪" # ═══════════════════════════════════════════════════════════ # 6. 检查 MySQL (可选) # ═══════════════════════════════════════════════════════════ step "检查数据库 (可选)" if command -v mysql &>/dev/null; then ok "MySQL: $(mysql --version 2>&1)" if pgrep mysqld > /dev/null 2>&1 || pgrep mariadbd > /dev/null 2>&1; then ok "MySQL 服务运行中" mysql -u root -e "CREATE DATABASE IF NOT EXISTS futureoss CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" 2>/dev/null && \ ok "数据库 futureoss 已就绪" || \ warn "无法创建数据库,请检查权限" else warn "MySQL 服务未运行" info "启动命令: sudo systemctl start mysql (或 mariadb)" fi else info "MySQL 未安装 (可选功能)" info "安装: sudo apt install mysql-server (Debian/Ubuntu)" info " sudo yum install mysql-server (CentOS/RHEL)" fi # ═══════════════════════════════════════════════════════════ # 7. 启动服务 # ═══════════════════════════════════════════════════════════ step "启动 FutureOSS" if $DAEMON; then LOG_FILE="logs/futureoss.log" PID_FILE="logs/futureoss.pid" if [[ -f "$PID_FILE" ]]; then OLD_PID=$(cat "$PID_FILE") if kill -0 "$OLD_PID" 2>/dev/null; then warn "已有进程运行 (PID: $OLD_PID)" info "停止: kill $OLD_PID 或 bash start.sh stop" exit 0 fi fi nohup $PYTHON_CMD -m oss.cli serve > "$LOG_FILE" 2>&1 & NEW_PID=$! echo "$NEW_PID" > "$PID_FILE" ok "守护进程已启动 (PID: $NEW_PID)" info "日志文件: $LOG_FILE" sleep 2 curl -s http://localhost:8080/health &>/dev/null && ok "服务就绪: http://localhost:8080" || warn "服务启动中,请稍候..." exit 0 fi # 前台模式 + 崩溃自动重启 echo -e "${WHITE}运行中... 按 Ctrl+C 停止${NC}" echo "" RESTART_DELAY=3 RESTART_COUNT=0 while true; do $PYTHON_CMD -m oss.cli serve EXIT_CODE=$? if [[ $EXIT_CODE -eq 0 ]]; then ok "服务正常退出" break fi RESTART_COUNT=$((RESTART_COUNT + 1)) warn "服务异常退出 (code: $EXIT_CODE),${RESTART_DELAY}s 后重启... (第 $RESTART_COUNT 次)" sleep $RESTART_DELAY # 指数退避 if [[ $RESTART_DELAY -lt 30 ]]; then RESTART_DELAY=$((RESTART_DELAY * 2)) fi done deactivate 2>/dev/null || true