Files
NebulaShell/start.sh
2026-04-17 23:15:15 +08:00

323 lines
12 KiB
Bash
Executable File
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
# ═══════════════════════════════════════════════════════════
# 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<filled; i++)); do bar+="█"; done
for ((i=0; i<empty; i++)); do bar+="░"; done
echo -ne "\r ${GREEN}[${bar}]${NC} ${WHITE}${pct}%${NC} - ${label}"
}
# ── Logo ──
echo -e "${BOLD}${CYAN}"
echo " ███████╗ ██████╗ ██████╗ ██████╗ ██████╗ ██████╗ "
echo " ██╔════╝ ██╔══██╗ ██╔══██╗ ██╔══██╗ ██╔══██╗██╔════╝ "
echo " █████╗ ██████╔╝ ██████╔╝ ██████╔╝ ██║ ██║██║ ███╗"
echo " ██╔══╝ ██╔══██╗ ██╔══██╗ ██╔══██╗ ██║ ██║██║ ██║"
echo " ██║ ██║ ██║ ██║ ██║ ██║ ██║ ██████╔╝╚██████╔╝"
echo " ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═════╝ "
echo -e "${NC}"
echo -e "${WHITE} 开发者通用工具套组 · 一切皆为插件${NC}"
echo -e "${WHITE} https://gitee.com/starlight-apk/feature-oss${NC}"
echo ""
# ── 守护模式 ──
DAEMON=false
if [[ "$1" == "--daemon" || "$1" == "-d" ]]; then
DAEMON=true
fi
# ═══════════════════════════════════════════════════════════
# 1. 检测操作系统
# ═══════════════════════════════════════════════════════════
step "环境检测"
PKG_MANAGER=""
OS_NAME=""
if [[ -f /etc/os-release ]]; then
OS_NAME=$(. /etc/os-release && echo "$PRETTY_NAME")
info "操作系统: $OS_NAME"
fi
if command -v apt-get &>/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 "安装系统依赖"
DEPENDENCIES=("git" "curl" "wget" "php" "php-cli" "php-mbstring" "php-xml" "php-zip" "python3" "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"
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"
# ═══════════════════════════════════════════════════════════
# 4. 安装 Python 依赖
# ═══════════════════════════════════════════════════════════
step "安装 Python 依赖"
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"
$PYTHON_CMD -c "import $pkg" 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/pkg" "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