#!/bin/bash set -e # 解决 macOS 下 tr 可能出现的非法字节序列问题 export LANG=en_US.UTF-8 export LC_ALL=C # GitHub repo used for release downloads REPO="Sagit-chu/flux-panel" COUNTRY=$(curl -s https://ipinfo.io/country) maybe_proxy_url() { local url="$1" if [ "$COUNTRY" = "CN" ]; then echo "https://ghfast.top/${url}" else echo "$url" fi } resolve_latest_release_tag() { local effective_url tag api_tag latest_url api_url latest_url="https://github.com/${REPO}/releases/latest" api_url="https://api.github.com/repos/${REPO}/releases/latest" effective_url=$(curl -fsSL -o /dev/null -w '%{url_effective}' -L "$latest_url" 2>/dev/null || true) tag="${effective_url##*/}" if [[ -n "$tag" && "$tag" != "latest" ]]; then echo "$tag" return 0 fi if [ "$COUNTRY" = "CN" ]; then effective_url=$(curl -fsSL -o /dev/null -w '%{url_effective}' -L "$(maybe_proxy_url "$latest_url")" 2>/dev/null || true) tag="${effective_url##*/}" if [[ -n "$tag" && "$tag" != "latest" ]]; then echo "$tag" return 0 fi fi api_tag=$(curl -fsSL "$api_url" 2>/dev/null | grep -m1 '"tag_name"' | sed -E 's/.*"tag_name"[[:space:]]*:[[:space:]]*"([^"]+)".*/\1/' || true) if [[ -n "$api_tag" ]]; then echo "$api_tag" return 0 fi if [ "$COUNTRY" = "CN" ]; then api_tag=$(curl -fsSL "$(maybe_proxy_url "$api_url")" 2>/dev/null | grep -m1 '"tag_name"' | sed -E 's/.*"tag_name"[[:space:]]*:[[:space:]]*"([^"]+)".*/\1/' || true) if [[ -n "$api_tag" ]]; then echo "$api_tag" return 0 fi fi return 1 } resolve_version() { if [[ -n "${VERSION:-}" ]]; then echo "$VERSION" return 0 fi if [[ -n "${FLUX_VERSION:-}" ]]; then echo "$FLUX_VERSION" return 0 fi if resolve_latest_release_tag; then return 0 fi echo "❌ 无法获取最新版本号。你可以手动指定版本,例如:VERSION=<版本号> ./panel_install.sh" >&2 return 1 } # 全局下载地址配置(默认获取最新版本;也可用 VERSION=... 覆盖) RESOLVED_VERSION=$(resolve_version) || exit 1 DOCKER_COMPOSEV4_URL=$(maybe_proxy_url "https://github.com/${REPO}/releases/download/${RESOLVED_VERSION}/docker-compose-v4.yml") DOCKER_COMPOSEV6_URL=$(maybe_proxy_url "https://github.com/${REPO}/releases/download/${RESOLVED_VERSION}/docker-compose-v6.yml") # 根据IPv6支持情况选择docker-compose URL get_docker_compose_url() { if check_ipv6_support > /dev/null 2>&1; then echo "$DOCKER_COMPOSEV6_URL" else echo "$DOCKER_COMPOSEV4_URL" fi } # 检查 docker-compose 或 docker compose 命令 check_docker() { if command -v docker-compose &> /dev/null; then DOCKER_CMD="docker-compose" elif command -v docker &> /dev/null; then if docker compose version &> /dev/null; then DOCKER_CMD="docker compose" else echo "错误:检测到 docker,但不支持 'docker compose' 命令。请安装 docker-compose 或更新 docker 版本。" exit 1 fi else echo "错误:未检测到 docker 或 docker-compose 命令。请先安装 Docker。" exit 1 fi echo "检测到 Docker 命令:$DOCKER_CMD" } # 检测系统是否支持 IPv6 check_ipv6_support() { echo "🔍 检测 IPv6 支持..." # 检查是否有 IPv6 地址(排除 link-local 地址) if ip -6 addr show | grep -v "scope link" | grep -q "inet6"; then echo "✅ 检测到系统支持 IPv6" return 0 elif ifconfig 2>/dev/null | grep -v "fe80:" | grep -q "inet6"; then echo "✅ 检测到系统支持 IPv6" return 0 else echo "⚠️ 未检测到 IPv6 支持" return 1 fi } # 配置 Docker 启用 IPv6 configure_docker_ipv6() { echo "🔧 配置 Docker IPv6 支持..." # 检查操作系统类型 OS_TYPE=$(uname -s) if [[ "$OS_TYPE" == "Darwin" ]]; then # macOS 上 Docker Desktop 已默认支持 IPv6 echo "✅ macOS Docker Desktop 默认支持 IPv6" return 0 fi # Docker daemon 配置文件路径 DOCKER_CONFIG="/etc/docker/daemon.json" # 检查是否需要 sudo if [[ $EUID -ne 0 ]]; then SUDO_CMD="sudo" else SUDO_CMD="" fi # 检查 Docker 配置文件 if [ -f "$DOCKER_CONFIG" ]; then # 检查是否已经配置了 IPv6 if grep -q '"ipv6"' "$DOCKER_CONFIG"; then echo "✅ Docker 已配置 IPv6 支持" else echo "📝 更新 Docker 配置以启用 IPv6..." # 备份原配置 $SUDO_CMD cp "$DOCKER_CONFIG" "${DOCKER_CONFIG}.backup" # 使用 jq 或 sed 添加 IPv6 配置 if command -v jq &> /dev/null; then $SUDO_CMD jq '. + {"ipv6": true, "fixed-cidr-v6": "fd00::/80"}' "$DOCKER_CONFIG" > /tmp/daemon.json && $SUDO_CMD mv /tmp/daemon.json "$DOCKER_CONFIG" else # 如果没有 jq,使用 sed $SUDO_CMD sed -i 's/^{$/{\n "ipv6": true,\n "fixed-cidr-v6": "fd00::\/80",/' "$DOCKER_CONFIG" fi echo "🔄 重启 Docker 服务..." if command -v systemctl &> /dev/null; then $SUDO_CMD systemctl restart docker elif command -v service &> /dev/null; then $SUDO_CMD service docker restart else echo "⚠️ 请手动重启 Docker 服务" fi sleep 5 fi else # 创建新的配置文件 echo "📝 创建 Docker 配置文件..." $SUDO_CMD mkdir -p /etc/docker echo '{ "ipv6": true, "fixed-cidr-v6": "fd00::/80" }' | $SUDO_CMD tee "$DOCKER_CONFIG" > /dev/null echo "🔄 重启 Docker 服务..." if command -v systemctl &> /dev/null; then $SUDO_CMD systemctl restart docker elif command -v service &> /dev/null; then $SUDO_CMD service docker restart else echo "⚠️ 请手动重启 Docker 服务" fi sleep 5 fi } # 显示菜单 show_menu() { echo "===============================================" echo " 面板管理脚本" echo "===============================================" echo "请选择操作:" echo "1. 安装面板" echo "2. 更新面板" echo "3. 卸载面板" echo "4. 退出" echo "===============================================" } generate_random() { LC_ALL=C tr -dc 'A-Za-z0-9' /dev/null || realpath "$0" 2>/dev/null || echo "$0")" sleep 1 rm -f "$SCRIPT_PATH" && echo "✅ 脚本文件已删除" || echo "❌ 删除脚本文件失败" } # 获取用户输入的配置参数 get_config_params() { echo "🔧 请输入配置参数:" read -p "前端端口(默认 6366): " FRONTEND_PORT FRONTEND_PORT=${FRONTEND_PORT:-6366} read -p "后端端口(默认 6365): " BACKEND_PORT BACKEND_PORT=${BACKEND_PORT:-6365} # 生成JWT密钥 JWT_SECRET=$(generate_random) } # 安装功能 install_panel() { echo "🚀 开始安装面板..." check_docker get_config_params echo "🔽 下载必要文件..." DOCKER_COMPOSE_URL=$(get_docker_compose_url) echo "📡 选择配置文件:$(basename "$DOCKER_COMPOSE_URL")" curl -L -o docker-compose.yml "$DOCKER_COMPOSE_URL" echo "✅ 文件准备完成" # 自动检测并配置 IPv6 支持 if check_ipv6_support; then echo "🚀 系统支持 IPv6,自动启用 IPv6 配置..." configure_docker_ipv6 fi cat > .env </dev/null || true docker stop -t 10 vite-frontend 2>/dev/null || true # 等待 WAL 文件同步 echo "⏳ 等待数据同步..." sleep 5 # 然后再完全停止 $DOCKER_CMD down echo "⬇️ 拉取最新镜像..." $DOCKER_CMD pull echo "🚀 启动更新后的服务..." $DOCKER_CMD up -d # 等待服务启动 echo "⏳ 等待服务启动..." # 检查后端容器健康状态 echo "🔍 检查后端服务状态..." for i in {1..90}; do if docker ps --format "{{.Names}}" | grep -q "^springboot-backend$"; then BACKEND_HEALTH=$(docker inspect -f '{{.State.Health.Status}}' springboot-backend 2>/dev/null || echo "unknown") if [[ "$BACKEND_HEALTH" == "healthy" ]]; then echo "✅ 后端服务健康检查通过" break elif [[ "$BACKEND_HEALTH" == "starting" ]]; then # 继续等待 : elif [[ "$BACKEND_HEALTH" == "unhealthy" ]]; then echo "⚠️ 后端健康状态:$BACKEND_HEALTH" fi else echo "⚠️ 后端容器未找到或未运行" BACKEND_HEALTH="not_running" fi if [ $i -eq 90 ]; then echo "❌ 后端服务启动超时(90秒)" echo "🔍 当前状态:$(docker inspect -f '{{.State.Health.Status}}' springboot-backend 2>/dev/null || echo '容器不存在')" echo "🛑 更新终止" return 1 fi # 每15秒显示一次进度 if [ $((i % 15)) -eq 1 ]; then echo "⏳ 等待后端服务启动... ($i/90) 状态:${BACKEND_HEALTH:-unknown}" fi sleep 1 done echo "✅ 更新完成" } # 卸载功能 uninstall_panel() { echo "🗑️ 开始卸载面板..." check_docker if [[ ! -f "docker-compose.yml" ]]; then echo "⚠️ 未找到 docker-compose.yml 文件,正在下载以完成卸载..." DOCKER_COMPOSE_URL=$(get_docker_compose_url) echo "📡 选择配置文件:$(basename "$DOCKER_COMPOSE_URL")" curl -L -o docker-compose.yml "$DOCKER_COMPOSE_URL" echo "✅ docker-compose.yml 下载完成" fi read -p "确认卸载面板吗?此操作将停止并删除所有容器和数据 (y/N): " confirm if [[ "$confirm" != "y" && "$confirm" != "Y" ]]; then echo "❌ 取消卸载" return 0 fi echo "🛑 停止并删除容器、镜像、卷..." $DOCKER_CMD down --rmi all --volumes --remove-orphans echo "🧹 删除配置文件..." rm -f docker-compose.yml .env echo "✅ 卸载完成" } # 主逻辑 main() { # 显示交互式菜单 while true; do show_menu read -p "请输入选项 (1-5): " choice case $choice in 1) install_panel delete_self exit 0 ;; 2) update_panel delete_self exit 0 ;; 3) uninstall_panel delete_self exit 0 ;; 4) echo "👋 退出脚本" delete_self exit 0 ;; *) echo "❌ 无效选项,请输入 1-5" echo "" ;; esac done } # 执行主函数 main