#!/bin/bash # dashGO 一键安装脚本 # 支持面板和 Agent 的完整部署 # 支持预编译二进制文件和源码构建两种方式 # 预编译文件下载地址: https://download.sharon.wiki/ # 用法: curl -sL https://raw.githubusercontent.com/ZYHUO/dashgo/main/install.sh | bash # 或者: bash install.sh [panel|agent|all] set -e VERSION='v1.2.0' GITHUB_REPO="ZYHUO/dashGO" GH_PROXY='https://hub.glowp.xyz/' INSTALL_DIR="/opt/dashgo" AGENT_DIR="/opt/dashgo-agent" TEMP_DIR="/tmp/dashgo-install" SINGBOX_DIR="/etc/sing-box" SINGBOX_DEFAULT_VERSION="1.12.0" # 颜色定义 RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[0;33m' BLUE='\033[0;34m' PURPLE='\033[0;35m' CYAN='\033[0;36m' NC='\033[0m' # 日志函数 log_info() { echo -e "${GREEN}[INFO]${NC} $1"; } log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } log_error() { echo -e "${RED}[ERROR]${NC} $1"; } log_hint() { echo -e "${BLUE}[HINT]${NC} $1"; } log_success() { echo -e "${PURPLE}[SUCCESS]${NC} $1"; } # 清理函数 cleanup() { rm -rf "$TEMP_DIR" >/dev/null 2>&1 || true } trap cleanup EXIT # 显示 Banner show_banner() { echo -e "${CYAN}" cat << 'EOF' ██████╗ █████╗ ███████╗██╗ ██╗ ██████╗ ██████╗ ██╔══██╗██╔══██╗██╔════╝██║ ██║██╔════╝ ██╔═══██╗ ██║ ██║███████║███████╗███████║██║ ███╗██║ ██║ ██║ ██║██╔══██║╚════██║██╔══██║██║ ██║██║ ██║ ██████╔╝██║ ██║███████║██║ ██║╚██████╔╝╚██████╔╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚═════╝ EOF echo -e "${NC}" echo -e "${GREEN}dashGO 一键安装脚本 ${VERSION}${NC}" echo -e "${BLUE}现代化的个人面板解决方案${NC}" echo "" } # 显示菜单 show_menu() { echo "请选择安装选项:" echo "" echo " 1) 安装面板 (Panel)" echo " 2) 安装节点 (Agent)" echo " 3) 完整安装 (Panel + Agent)" echo " 4) 卸载面板" echo " 5) 卸载节点" echo " 6) 更新面板" echo " 7) 更新节点" echo " 0) 退出" echo "" read -p "请输入选项 [0-7]: " choice case $choice in 1) install_panel ;; 2) install_agent_interactive ;; 3) install_all ;; 4) uninstall_panel ;; 5) uninstall_agent ;; 6) update_panel ;; 7) update_agent ;; 0) exit 0 ;; *) log_error "无效选项"; show_menu ;; esac } # 检查 root 权限 check_root() { if [ "$EUID" -ne 0 ]; then log_error "请使用 root 用户运行此脚本" exit 1 fi } # 检测系统架构 detect_arch() { ARCH=$(uname -m) case $ARCH in x86_64|amd64) ARCH="amd64"; SINGBOX_ARCH="amd64" ;; aarch64|arm64) ARCH="arm64"; SINGBOX_ARCH="arm64" ;; armv7l) ARCH="armv7"; SINGBOX_ARCH="armv7" ;; *) log_error "不支持的架构: $ARCH"; exit 1 ;; esac log_info "检测到架构: $ARCH" } # 检测操作系统 detect_os() { if [ -f /etc/os-release ]; then . /etc/os-release OS=$ID OS_VERSION=$VERSION_ID elif [ -f /etc/redhat-release ]; then OS="centos" else log_error "无法检测操作系统" exit 1 fi log_info "检测到系统: $OS $OS_VERSION" case $OS in debian|ubuntu) PKG_UPDATE="apt-get update -qq" PKG_INSTALL="apt-get install -y -qq" ;; centos|rhel|rocky|alma|fedora) PKG_UPDATE="yum makecache -q" PKG_INSTALL="yum install -y -q" ;; alpine) PKG_UPDATE="apk update" PKG_INSTALL="apk add --no-cache" ;; *) log_warn "未知的包管理器" ;; esac } # 检查 GitHub CDN check_cdn() { if wget --spider --quiet --timeout=3 "${GH_PROXY}https://raw.githubusercontent.com/SagerNet/sing-box/main/README.md" 2>/dev/null; then log_info "使用 GitHub 代理: $GH_PROXY" else GH_PROXY="" log_info "直连 GitHub" fi } # 端口检测功能 # 使用 netstat 检测端口 check_port_netstat() { local port=$1 local result_file=$2 if ! command -v netstat >/dev/null 2>&1; then echo "ERROR:netstat command not found" > "$result_file" return 1 fi local output if output=$(netstat -tlnp 2>/dev/null); then if echo "$output" | grep -q ":${port} .*LISTEN"; then local process_info=$(echo "$output" | grep ":${port} .*LISTEN" | awk '{print $NF}' | head -1) echo "OCCUPIED:netstat:${process_info}" > "$result_file" return 1 else echo "AVAILABLE:netstat:" > "$result_file" return 0 fi else echo "ERROR:netstat execution failed" > "$result_file" return 1 fi } # 使用 lsof 检测端口 check_port_lsof() { local port=$1 local result_file=$2 if ! command -v lsof >/dev/null 2>&1; then echo "ERROR:lsof command not found" > "$result_file" return 1 fi local output if output=$(lsof -i ":${port}" 2>/dev/null); then if echo "$output" | grep -q "LISTEN"; then local process_info=$(echo "$output" | grep "LISTEN" | awk '{print $1"/"$2}' | head -1) echo "OCCUPIED:lsof:${process_info}" > "$result_file" return 1 else echo "AVAILABLE:lsof:" > "$result_file" return 0 fi else # lsof returns 1 when no processes found, which is normal echo "AVAILABLE:lsof:" > "$result_file" return 0 fi } # 使用 ss 检测端口 check_port_ss() { local port=$1 local result_file=$2 if ! command -v ss >/dev/null 2>&1; then echo "ERROR:ss command not found" > "$result_file" return 1 fi local output if output=$(ss -tlnp 2>/dev/null); then if echo "$output" | grep -q ":${port} .*LISTEN"; then local process_info=$(echo "$output" | grep ":${port} .*LISTEN" | sed 's/.*users:((\([^)]*\)).*/\1/' | head -1) echo "OCCUPIED:ss:${process_info}" > "$result_file" return 1 else echo "AVAILABLE:ss:" > "$result_file" return 0 fi else echo "ERROR:ss execution failed" > "$result_file" return 1 fi } # 健壮的端口检测函数 robust_port_check() { local port=$1 local temp_dir="/tmp/dashgo-port-check-$$" # 验证端口号 if [ -z "$port" ] || [ "$port" -lt 1 ] || [ "$port" -gt 65535 ]; then log_error "无效的端口号: $port" return 2 fi mkdir -p "$temp_dir" log_info "检测端口 $port 可用性..." # 并行运行三种检测方法 check_port_netstat "$port" "$temp_dir/netstat.result" & local netstat_pid=$! check_port_lsof "$port" "$temp_dir/lsof.result" & local lsof_pid=$! check_port_ss "$port" "$temp_dir/ss.result" & local ss_pid=$! # 等待所有检测完成(最多5秒) local timeout=5 local elapsed=0 while [ $elapsed -lt $timeout ]; do if ! kill -0 $netstat_pid 2>/dev/null && ! kill -0 $lsof_pid 2>/dev/null && ! kill -0 $ss_pid 2>/dev/null; then break fi sleep 0.1 elapsed=$((elapsed + 1)) done # 强制终止未完成的进程 kill $netstat_pid $lsof_pid $ss_pid 2>/dev/null || true wait 2>/dev/null || true # 分析结果 local available_count=0 local occupied_count=0 local error_count=0 local process_info="" local methods_used="" for method in netstat lsof ss; do local result_file="$temp_dir/${method}.result" if [ -f "$result_file" ]; then local result=$(cat "$result_file") local status=$(echo "$result" | cut -d: -f1) local method_name=$(echo "$result" | cut -d: -f2) local proc_info=$(echo "$result" | cut -d: -f3) methods_used="${methods_used}${method_name} " case "$status" in "AVAILABLE") available_count=$((available_count + 1)) log_info " ✓ $method_name: 端口可用" ;; "OCCUPIED") occupied_count=$((occupied_count + 1)) if [ -n "$proc_info" ]; then process_info="$proc_info" fi log_warn " ✗ $method_name: 端口被占用 ($proc_info)" ;; "ERROR") error_count=$((error_count + 1)) log_warn " ! $method_name: 检测失败 ($proc_info)" ;; esac else error_count=$((error_count + 1)) log_warn " ! $method: 检测超时" fi done # 清理临时文件 rm -rf "$temp_dir" 2>/dev/null || true # 决策逻辑 local total_valid=$((available_count + occupied_count)) if [ $total_valid -eq 0 ]; then log_error "所有端口检测方法都失败了" log_hint "请手动检查端口 $port 是否被占用" return 2 fi # 记录诊断信息 log_info "端口检测结果汇总:" log_info " 使用方法: $methods_used" log_info " 可用投票: $available_count" log_info " 占用投票: $occupied_count" log_info " 失败次数: $error_count" if [ $occupied_count -gt $available_count ]; then log_error "端口 $port 被占用" if [ -n "$process_info" ]; then log_error "占用进程: $process_info" fi log_hint "请停止占用端口的进程或选择其他端口" return 1 elif [ $available_count -gt 0 ]; then log_success "端口 $port 可用" return 0 else log_warn "端口检测结果不确定,建议手动验证" return 2 fi } # 安装依赖 install_deps() { log_info "安装系统依赖..." local deps_needed="" command -v curl >/dev/null 2>&1 || deps_needed="$deps_needed curl" command -v wget >/dev/null 2>&1 || deps_needed="$deps_needed wget" command -v tar >/dev/null 2>&1 || deps_needed="$deps_needed tar" command -v unzip >/dev/null 2>&1 || deps_needed="$deps_needed unzip" command -v git >/dev/null 2>&1 || deps_needed="$deps_needed git" command -v rsync >/dev/null 2>&1 || deps_needed="$deps_needed rsync" if [ -n "$deps_needed" ]; then log_info "安装: $deps_needed" $PKG_UPDATE >/dev/null 2>&1 || true $PKG_INSTALL $deps_needed >/dev/null 2>&1 || log_warn "部分依赖安装失败" fi } # 安装 Node.js 和 npm install_nodejs() { if command -v node &>/dev/null && command -v npm &>/dev/null; then local node_version=$(node -v 2>/dev/null) log_info "Node.js 已安装: $node_version" return 0 fi log_info "安装 Node.js 和 npm..." case $OS in debian|ubuntu) # 使用 NodeSource 仓库安装最新 LTS 版本 curl -fsSL https://deb.nodesource.com/setup_lts.x | bash - $PKG_INSTALL nodejs ;; centos|rhel|rocky|alma|fedora) # 使用 NodeSource 仓库 curl -fsSL https://rpm.nodesource.com/setup_lts.x | bash - $PKG_INSTALL nodejs ;; alpine) $PKG_INSTALL nodejs npm ;; *) log_warn "未知系统,尝试通用安装方法..." # 使用 nvm 安装 curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash export NVM_DIR="$HOME/.nvm" [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" nvm install --lts ;; esac if command -v node &>/dev/null; then log_success "Node.js 安装完成: $(node -v)" log_success "npm 安装完成: $(npm -v)" else log_error "Node.js 安装失败" return 1 fi } # 构建前端 build_frontend() { local web_dir="$1" if [ ! -d "$web_dir" ]; then log_warn "前端目录不存在: $web_dir" return 0 fi log_info "开始构建前端..." cd "$web_dir" # 检查是否有 package.json if [ ! -f "package.json" ]; then log_warn "未找到 package.json,跳过前端构建" return 0 fi # 安装依赖 log_info "安装前端依赖 (这可能需要几分钟)..." if command -v pnpm &>/dev/null; then log_info "使用 pnpm 安装依赖..." pnpm install --frozen-lockfile 2>&1 | grep -v "^npm WARN" || npm install --legacy-peer-deps elif command -v yarn &>/dev/null; then log_info "使用 yarn 安装依赖..." yarn install --frozen-lockfile 2>&1 | grep -v "^npm WARN" || npm install --legacy-peer-deps else log_info "使用 npm 安装依赖..." npm install --legacy-peer-deps 2>&1 | grep -v "^npm WARN" fi if [ $? -ne 0 ]; then log_warn "依赖安装可能有警告,继续构建..." fi # 构建 log_info "构建前端资源..." if npm run build 2>&1 | tee /tmp/build.log | grep -v "^npm WARN"; then log_success "前端构建完成" # 检查构建产物 if [ -d "dist" ]; then log_info "构建产物位于: $web_dir/dist" else log_warn "未找到 dist 目录,构建可能失败" fi else log_error "前端构建失败,查看日志: /tmp/build.log" log_warn "将继续安装,但前端可能无法正常显示" fi cd - >/dev/null } # 安装 Docker install_docker() { if command -v docker &>/dev/null; then log_info "Docker 已安装" return 0 fi log_info "安装 Docker..." curl -fsSL https://get.docker.com | sh systemctl enable docker systemctl start docker log_success "Docker 安装完成" } # 安装 Docker Compose install_docker_compose() { if docker compose version &>/dev/null 2>&1; then log_info "Docker Compose 已安装" return 0 fi log_info "安装 Docker Compose..." local COMPOSE_VERSION="2.24.0" local COMPOSE_URL="https://github.com/docker/compose/releases/download/v${COMPOSE_VERSION}/docker-compose-linux-${ARCH}" if [ -n "$GH_PROXY" ]; then COMPOSE_URL="${GH_PROXY}${COMPOSE_URL}" fi curl -L "$COMPOSE_URL" -o /usr/local/bin/docker-compose chmod +x /usr/local/bin/docker-compose log_success "Docker Compose 安装完成" } # ==================== 端口检测功能 ==================== # 使用 netstat 检测端口 check_port_netstat() { local port="$1" local result="" local process_info="" if command -v netstat >/dev/null 2>&1; then # 检查 TCP 端口 (支持多种格式) result=$(netstat -tlnp 2>/dev/null | grep -E ":${port}[[:space:]]|:${port}$" | head -1) if [ -n "$result" ]; then process_info=$(echo "$result" | awk '{print $7}' | head -1) echo "netstat TCP result: $result" >> "$detection_log" 2>/dev/null || true return 1 # 端口被占用 fi # 检查 UDP 端口 result=$(netstat -ulnp 2>/dev/null | grep -E ":${port}[[:space:]]|:${port}$" | head -1) if [ -n "$result" ]; then process_info=$(echo "$result" | awk '{print $6}' | head -1) echo "netstat UDP result: $result" >> "$detection_log" 2>/dev/null || true return 1 # 端口被占用 fi # 在某些系统上,也检查 -an 参数的输出 result=$(netstat -an 2>/dev/null | grep -E ":${port}[[:space:]].*LISTEN" | head -1) if [ -n "$result" ]; then echo "netstat -an result: $result" >> "$detection_log" 2>/dev/null || true return 1 # 端口被占用 fi return 0 # 端口可用 else return 2 # 命令不可用 fi } # 使用 lsof 检测端口 check_port_lsof() { local port="$1" local result="" if command -v lsof >/dev/null 2>&1; then # 检查 TCP 和 UDP 端口 result=$(lsof -i ":${port}" -sTCP:LISTEN 2>/dev/null) if [ -n "$result" ]; then echo "lsof TCP result: $result" >> "$detection_log" 2>/dev/null || true return 1 # 端口被占用 fi # 也检查所有协议的端口使用情况 result=$(lsof -i ":${port}" 2>/dev/null | grep -v "^COMMAND") if [ -n "$result" ]; then echo "lsof general result: $result" >> "$detection_log" 2>/dev/null || true return 1 # 端口被占用 fi return 0 # 端口可用 else return 2 # 命令不可用 fi } # 使用 ss 检测端口 check_port_ss() { local port="$1" local result="" if command -v ss >/dev/null 2>&1; then # 检查 TCP 监听端口 result=$(ss -tlnp 2>/dev/null | grep -E ":${port}[[:space:]]|:${port}$") if [ -n "$result" ]; then echo "ss TCP result: $result" >> "$detection_log" 2>/dev/null || true return 1 # 端口被占用 fi # 检查 UDP 监听端口 result=$(ss -ulnp 2>/dev/null | grep -E ":${port}[[:space:]]|:${port}$") if [ -n "$result" ]; then echo "ss UDP result: $result" >> "$detection_log" 2>/dev/null || true return 1 # 端口被占用 fi # 检查所有状态的端口 result=$(ss -anp 2>/dev/null | grep -E ":${port}[[:space:]].*LISTEN") if [ -n "$result" ]; then echo "ss general result: $result" >> "$detection_log" 2>/dev/null || true return 1 # 端口被占用 fi return 0 # 端口可用 else return 2 # 命令不可用 fi } # 综合端口检测函数 check_port_availability() { local port="$1" local method_used="" local detection_log="/tmp/port_detection_${port}.log" local available=true local error_msg="" local process_info="" local consensus_available=0 local consensus_occupied=0 # 验证端口号有效性 if [ -z "$port" ] || [ "$port" -lt 1 ] || [ "$port" -gt 65535 ]; then log_error "无效的端口号: $port" return 1 fi # 清空日志文件 > "$detection_log" log_info "检测端口 ${port} 可用性..." | tee -a "$detection_log" # 方法1: 使用 netstat log_info "尝试使用 netstat 检测端口 ${port}..." | tee -a "$detection_log" check_port_netstat "$port" local netstat_result=$? case $netstat_result in 0) log_info "netstat: 端口 ${port} 可用" | tee -a "$detection_log" consensus_available=$((consensus_available + 1)) method_used="netstat" ;; 1) log_warn "netstat: 端口 ${port} 被占用" | tee -a "$detection_log" consensus_occupied=$((consensus_occupied + 1)) method_used="netstat" ;; 2) log_warn "netstat: 命令不可用" | tee -a "$detection_log" ;; esac # 方法2: 使用 lsof log_info "尝试使用 lsof 检测端口 ${port}..." | tee -a "$detection_log" check_port_lsof "$port" local lsof_result=$? case $lsof_result in 0) log_info "lsof: 端口 ${port} 可用" | tee -a "$detection_log" consensus_available=$((consensus_available + 1)) if [ $netstat_result -eq 2 ]; then method_used="lsof" fi ;; 1) log_warn "lsof: 端口 ${port} 被占用" | tee -a "$detection_log" consensus_occupied=$((consensus_occupied + 1)) if [ $netstat_result -eq 2 ]; then method_used="lsof" fi ;; 2) log_warn "lsof: 命令不可用" | tee -a "$detection_log" ;; esac # 方法3: 使用 ss log_info "尝试使用 ss 检测端口 ${port}..." | tee -a "$detection_log" check_port_ss "$port" local ss_result=$? case $ss_result in 0) log_info "ss: 端口 ${port} 可用" | tee -a "$detection_log" consensus_available=$((consensus_available + 1)) if [ $netstat_result -eq 2 ] && [ $lsof_result -eq 2 ]; then method_used="ss" fi ;; 1) log_warn "ss: 端口 ${port} 被占用" | tee -a "$detection_log" consensus_occupied=$((consensus_occupied + 1)) if [ $netstat_result -eq 2 ] && [ $lsof_result -eq 2 ]; then method_used="ss" fi ;; 2) log_warn "ss: 命令不可用" | tee -a "$detection_log" ;; esac # 如果所有传统方法都不可用,尝试使用 nc 进行连接测试 if [ $netstat_result -eq 2 ] && [ $lsof_result -eq 2 ] && [ $ss_result -eq 2 ]; then log_info "尝试使用 nc 进行连接测试..." | tee -a "$detection_log" if command -v nc >/dev/null 2>&1; then if timeout 3 nc -z localhost "$port" 2>/dev/null; then log_warn "nc: 端口 ${port} 被占用" | tee -a "$detection_log" consensus_occupied=$((consensus_occupied + 1)) method_used="nc" else log_info "nc: 端口 ${port} 可用" | tee -a "$detection_log" consensus_available=$((consensus_available + 1)) method_used="nc" fi else # 最后尝试使用 telnet if command -v telnet >/dev/null 2>&1; then log_info "尝试使用 telnet 进行连接测试..." | tee -a "$detection_log" if timeout 3 bash -c "echo '' | telnet localhost $port" 2>/dev/null | grep -q "Connected"; then log_warn "telnet: 端口 ${port} 被占用" | tee -a "$detection_log" consensus_occupied=$((consensus_occupied + 1)) method_used="telnet" else log_info "telnet: 端口 ${port} 可用" | tee -a "$detection_log" consensus_available=$((consensus_available + 1)) method_used="telnet" fi else error_msg="所有端口检测方法都不可用" log_error "$error_msg" | tee -a "$detection_log" return 1 fi fi fi # 基于共识决定最终结果 log_info "检测结果统计: 可用票数=${consensus_available}, 占用票数=${consensus_occupied}" | tee -a "$detection_log" if [ $consensus_available -eq 0 ] && [ $consensus_occupied -eq 0 ]; then log_error "所有检测方法都失败了" | tee -a "$detection_log" return 1 fi if [ $consensus_occupied -gt 0 ]; then available=false else available=true fi # 记录最终结果 if [ "$available" = true ]; then log_success "端口 ${port} 检测完成: 可用 (共识: ${consensus_available}/${consensus_occupied})" | tee -a "$detection_log" return 0 else log_error "端口 ${port} 检测完成: 被占用 (共识: ${consensus_available}/${consensus_occupied})" | tee -a "$detection_log" return 1 fi } # 安装端口检测工具 install_port_detection_tools() { log_info "检查并安装端口检测工具..." local tools_needed="" # 检查需要安装的工具 command -v netstat >/dev/null 2>&1 || tools_needed="$tools_needed net-tools" command -v lsof >/dev/null 2>&1 || tools_needed="$tools_needed lsof" command -v ss >/dev/null 2>&1 || tools_needed="$tools_needed iproute2" command -v nc >/dev/null 2>&1 || tools_needed="$tools_needed netcat" if [ -n "$tools_needed" ]; then log_info "需要安装的工具: $tools_needed" case $OS in debian|ubuntu) $PKG_UPDATE >/dev/null 2>&1 || true $PKG_INSTALL $tools_needed >/dev/null 2>&1 || log_warn "部分端口检测工具安装失败" ;; centos|rhel|rocky|alma|fedora) # CentOS/RHEL 中 iproute2 包名可能不同 tools_needed=$(echo "$tools_needed" | sed 's/iproute2/iproute/g') tools_needed=$(echo "$tools_needed" | sed 's/netcat/nc/g') $PKG_UPDATE >/dev/null 2>&1 || true $PKG_INSTALL $tools_needed >/dev/null 2>&1 || log_warn "部分端口检测工具安装失败" ;; alpine) tools_needed=$(echo "$tools_needed" | sed 's/net-tools/net-tools/g') tools_needed=$(echo "$tools_needed" | sed 's/iproute2/iproute2/g') tools_needed=$(echo "$tools_needed" | sed 's/netcat/netcat-openbsd/g') $PKG_UPDATE >/dev/null 2>&1 || true $PKG_INSTALL $tools_needed >/dev/null 2>&1 || log_warn "部分端口检测工具安装失败" ;; *) log_warn "未知系统,无法自动安装端口检测工具" ;; esac # 验证安装结果 local installed_count=0 command -v netstat >/dev/null 2>&1 && installed_count=$((installed_count + 1)) command -v lsof >/dev/null 2>&1 && installed_count=$((installed_count + 1)) command -v ss >/dev/null 2>&1 && installed_count=$((installed_count + 1)) command -v nc >/dev/null 2>&1 && installed_count=$((installed_count + 1)) log_info "端口检测工具安装完成,可用工具数量: $installed_count" else log_info "所有端口检测工具已安装" fi } # 安装 Alpine 调试工具 install_alpine_debug_tools() { if [ "$OS" != "alpine" ]; then return 0 fi log_info "安装 Alpine 调试工具..." local debug_tools="" # 检查调试工具 command -v curl >/dev/null 2>&1 || debug_tools="$debug_tools curl" command -v wget >/dev/null 2>&1 || debug_tools="$debug_tools wget" command -v bind-tools >/dev/null 2>&1 || debug_tools="$debug_tools bind-tools" command -v iputils >/dev/null 2>&1 || debug_tools="$debug_tools iputils" command -v strace >/dev/null 2>&1 || debug_tools="$debug_tools strace" command -v gcompat >/dev/null 2>&1 || debug_tools="$debug_tools gcompat" # 检查 musl 兼容性库 if [ ! -f /lib/libc.so.6 ] && ! apk info -e gcompat >/dev/null 2>&1; then debug_tools="$debug_tools gcompat" fi # 检查 CA 证书 if [ ! -d /etc/ssl/certs ] || [ -z "$(ls -A /etc/ssl/certs 2>/dev/null)" ]; then debug_tools="$debug_tools ca-certificates" fi if [ -n "$debug_tools" ]; then log_info "安装调试工具: $debug_tools" $PKG_UPDATE >/dev/null 2>&1 || true $PKG_INSTALL $debug_tools >/dev/null 2>&1 || log_warn "部分调试工具安装失败" # 更新 CA 证书 if echo "$debug_tools" | grep -q "ca-certificates"; then update-ca-certificates >/dev/null 2>&1 || true fi log_success "Alpine 调试工具安装完成" else log_info "所有 Alpine 调试工具已安装" fi # 检查 musl libc 兼容性 if [ -f /lib/ld-musl-*.so.1 ]; then log_info "musl libc 已安装: $(ls /lib/ld-musl-*.so.1)" else log_warn "未检测到 musl libc" fi # 检查 glibc 兼容层 if [ -f /lib/libc.so.6 ] || apk info -e gcompat >/dev/null 2>&1; then log_info "glibc 兼容层可用" else log_warn "未安装 glibc 兼容层,某些预编译程序可能无法运行" fi } # 端口检测诊断函数 diagnose_port_detection() { local port="$1" log_info "开始端口检测诊断..." echo "" echo "系统信息:" echo " 操作系统: $OS $OS_VERSION" echo " 架构: $ARCH" echo " 内核版本: $(uname -r 2>/dev/null || echo '未知')" echo "" echo "可用的端口检测工具:" command -v netstat >/dev/null 2>&1 && echo " ✓ netstat ($(netstat --version 2>&1 | head -1 | cut -d' ' -f1-2 || echo '版本未知'))" || echo " ✗ netstat (未安装)" command -v lsof >/dev/null 2>&1 && echo " ✓ lsof ($(lsof -v 2>&1 | head -1 || echo '版本未知'))" || echo " ✗ lsof (未安装)" command -v ss >/dev/null 2>&1 && echo " ✓ ss ($(ss -V 2>&1 | head -1 || echo '版本未知'))" || echo " ✗ ss (未安装)" command -v nc >/dev/null 2>&1 && echo " ✓ nc ($(nc -h 2>&1 | head -1 | cut -d' ' -f1-2 || echo '版本未知'))" || echo " ✗ nc (未安装)" command -v telnet >/dev/null 2>&1 && echo " ✓ telnet" || echo " ✗ telnet (未安装)" echo "" if [ -n "$port" ]; then echo "端口 ${port} 详细检测结果:" echo "" if command -v netstat >/dev/null 2>&1; then echo " netstat TCP 监听端口:" netstat -tlnp 2>/dev/null | grep -E ":${port}[[:space:]]|:${port}$" | head -5 | sed 's/^/ /' || echo " (无结果)" echo " netstat UDP 监听端口:" netstat -ulnp 2>/dev/null | grep -E ":${port}[[:space:]]|:${port}$" | head -5 | sed 's/^/ /' || echo " (无结果)" echo " netstat 所有端口状态:" netstat -an 2>/dev/null | grep -E ":${port}[[:space:]]" | head -5 | sed 's/^/ /' || echo " (无结果)" echo "" fi if command -v lsof >/dev/null 2>&1; then echo " lsof 端口使用情况:" lsof -i ":${port}" 2>/dev/null | head -10 | sed 's/^/ /' || echo " (无结果)" echo "" fi if command -v ss >/dev/null 2>&1; then echo " ss TCP 监听端口:" ss -tlnp 2>/dev/null | grep -E ":${port}[[:space:]]|:${port}$" | head -5 | sed 's/^/ /' || echo " (无结果)" echo " ss UDP 监听端口:" ss -ulnp 2>/dev/null | grep -E ":${port}[[:space:]]|:${port}$" | head -5 | sed 's/^/ /' || echo " (无结果)" echo "" fi if command -v nc >/dev/null 2>&1; then echo " nc 连接测试:" if timeout 3 nc -z localhost "$port" 2>/dev/null; then echo " 端口 ${port} 可连接 (被占用)" else echo " 端口 ${port} 不可连接 (可能可用)" fi echo "" fi echo "常见端口用途参考:" case $port in 80) echo " 端口 80: HTTP 网页服务" ;; 443) echo " 端口 443: HTTPS 安全网页服务" ;; 22) echo " 端口 22: SSH 远程登录" ;; 21) echo " 端口 21: FTP 文件传输" ;; 25) echo " 端口 25: SMTP 邮件发送" ;; 53) echo " 端口 53: DNS 域名解析" ;; 3306) echo " 端口 3306: MySQL 数据库" ;; 5432) echo " 端口 5432: PostgreSQL 数据库" ;; 6379) echo " 端口 6379: Redis 缓存" ;; 8080) echo " 端口 8080: 备用 HTTP 服务" ;; *) echo " 端口 ${port}: 自定义应用端口" ;; esac echo "" echo "检测日志文件: /tmp/port_detection_${port}.log" if [ -f "/tmp/port_detection_${port}.log" ]; then echo "日志文件大小: $(wc -l < "/tmp/port_detection_${port}.log") 行" fi echo "" echo "建议的解决方案:" echo " 1. 如果端口被占用,可以:" echo " - 停止占用端口的服务" echo " - 使用其他可用端口" echo " - 检查是否有僵尸进程占用端口" echo " 2. 如果检测工具不可用,可以安装:" echo " - Ubuntu/Debian: apt-get install net-tools lsof iproute2 netcat" echo " - CentOS/RHEL: yum install net-tools lsof iproute netcat" echo " 3. 如果仍有问题,请检查防火墙设置" fi } # ==================== 面板安装 ==================== # 统一检查所有端口 check_all_ports() { log_info "检查端口占用情况..." echo "" # 定义需要检查的端口 local -A ports=( [8080]="dashGO Server" [443]="HTTPS/Trojan/VLESS" [80]="HTTP" [8388]="Shadowsocks" [10086]="VMess" [9000]="SSMAPI" ) local conflicts=() local conflict_details=() # 检查每个端口 for port in "${!ports[@]}"; do local service="${ports[$port]}" local pid="" local process="" # 尝试检测端口占用 if command -v lsof >/dev/null 2>&1; then pid=$(lsof -ti :$port 2>/dev/null | head -1) elif command -v netstat >/dev/null 2>&1; then pid=$(netstat -tlnp 2>/dev/null | grep ":$port " | awk '{print $7}' | cut -d'/' -f1 | head -1) elif command -v ss >/dev/null 2>&1; then pid=$(ss -tlnp 2>/dev/null | grep ":$port " | awk '{print $6}' | grep -oP 'pid=\K[0-9]+' | head -1) fi if [ -n "$pid" ]; then process=$(ps -p $pid -o comm= 2>/dev/null || echo "Unknown") log_warn "端口 $port ($service) 被占用 - 进程: $process (PID: $pid)" conflicts+=("$port") conflict_details+=("$port:$pid:$process:$service") else log_info "端口 $port ($service) 可用" fi done # 如果有冲突,询问处理方式 if [ ${#conflicts[@]} -gt 0 ]; then echo "" log_error "发现 ${#conflicts[@]} 个端口被占用" echo "" echo "冲突端口列表:" for detail in "${conflict_details[@]}"; do IFS=':' read -r port pid process service <<< "$detail" echo " - 端口 $port ($service): $process (PID: $pid)" done echo "" read -p "是否自动停止这些进程?[y/N] " -n 1 -r echo if [[ $REPLY =~ ^[Yy]$ ]]; then for detail in "${conflict_details[@]}"; do IFS=':' read -r port pid process service <<< "$detail" log_info "停止进程 $pid ($process)..." if kill -9 $pid 2>/dev/null; then log_success "进程 $pid 已停止" else log_warn "停止进程 $pid 失败,可能需要 root 权限" fi done sleep 2 # 重新检查 log_info "重新检查端口..." local still_occupied=false for port in "${conflicts[@]}"; do if ! check_port_availability "$port" >/dev/null 2>&1; then log_error "端口 $port 仍被占用" still_occupied=true fi done if [ "$still_occupied" = true ]; then log_error "部分端口仍被占用,请手动处理" read -p "是否继续安装?[y/N] " -n 1 -r echo if [[ ! $REPLY =~ ^[Yy]$ ]]; then exit 1 fi else log_success "所有端口已释放" fi else log_warn "跳过自动停止进程" read -p "是否继续安装?(可能导致端口冲突) [y/N] " -n 1 -r echo if [[ ! $REPLY =~ ^[Yy]$ ]]; then exit 1 fi fi else log_success "所有端口检查通过" fi echo "" } # 安装面板 install_panel() { log_info "开始安装 dashGO 面板..." # 安装端口检测工具 install_port_detection_tools # 统一检查所有端口 check_all_ports # 询问数据库类型 echo "" echo "请选择数据库类型:" echo " 1) SQLite (推荐,轻量级,无需额外容器)" echo " 2) MySQL (使用外部 MySQL 数据库)" echo "" read -p "请选择 [1-2]: " db_type db_type=${db_type:-1} # 如果选择 MySQL,询问连接信息 if [ "$db_type" = "2" ]; then echo "" log_info "请输入 MySQL 数据库连接信息:" echo "" read -p "MySQL 主机地址 [默认: localhost]: " mysql_host mysql_host=${mysql_host:-localhost} read -p "MySQL 端口 [默认: 3306]: " mysql_port mysql_port=${mysql_port:-3306} read -p "MySQL 用户名 [默认: root]: " mysql_user mysql_user=${mysql_user:-root} read -sp "MySQL 密码: " mysql_password echo "" read -p "数据库名称 [默认: dashgo]: " mysql_database mysql_database=${mysql_database:-dashgo} echo "" log_info "请确保数据库 '${mysql_database}' 已创建" fi # 询问安装方式 echo "" echo "请选择安装方式:" echo " 1) 使用预编译版本 (推荐)" echo " 2) 从源码构建" echo "" read -p "请选择 [1-2]: " install_type install_type=${install_type:-1} # 询问 Web 端口和 SSL echo "" read -p "是否启用 HTTPS (443端口)? [y/N]: " enable_ssl enable_ssl=${enable_ssl:-N} if [ "$enable_ssl" = "y" ] || [ "$enable_ssl" = "Y" ]; then web_port=443 use_ssl="true" # 检测端口 443 可用性 log_info "检测 HTTPS 端口 443 可用性..." if check_port_availability 443; then log_success "端口 443 可用,可以启用 HTTPS" else log_error "端口 443 被占用,无法启用 HTTPS" echo "" echo "诊断信息:" diagnose_port_detection 443 echo "" echo "请选择处理方式:" echo " 1) 继续使用 HTTP (端口 80)" echo " 2) 手动指定其他端口" echo " 3) 退出安装,手动处理端口冲突" read -p "请选择 [1-3]: " port_conflict_action case $port_conflict_action in 1) log_info "切换到 HTTP 模式" web_port=80 use_ssl="false" # 检测端口 80 if ! check_port_availability 80; then log_error "端口 80 也被占用" diagnose_port_detection 80 read -p "请输入其他可用端口: " web_port if ! check_port_availability "$web_port"; then log_error "指定端口 $web_port 也被占用,请手动处理" exit 1 fi fi ;; 2) read -p "请输入 HTTPS 端口 [默认: 8443]: " web_port web_port=${web_port:-8443} if ! check_port_availability "$web_port"; then log_error "指定端口 $web_port 被占用" diagnose_port_detection "$web_port" exit 1 fi ;; 3) log_info "安装已取消" exit 0 ;; esac fi if [ "$use_ssl" = "true" ]; then echo "" echo "请选择证书类型:" echo " 1) 使用 Cloudflare Origin Certificate (推荐)" echo " 2) 使用自签名证书 (测试用)" echo " 3) 我已有证书文件" read -p "请选择 [1-3]: " cert_type cert_type=${cert_type:-1} fi else read -p "请输入 Web 访问端口 [默认: 80]: " web_port web_port=${web_port:-80} use_ssl="false" # 检测指定端口可用性 log_info "检测端口 ${web_port} 可用性..." if ! check_port_availability "$web_port"; then log_error "端口 ${web_port} 被占用" diagnose_port_detection "$web_port" echo "" read -p "请输入其他可用端口: " web_port if ! check_port_availability "$web_port"; then log_error "指定端口 $web_port 也被占用,请手动处理" exit 1 fi fi fi # 询问管理员账号 echo "" read -p "请输入管理员邮箱 [默认: admin@example.com]: " admin_email admin_email=${admin_email:-admin@example.com} echo "" read -p "请输入管理员密码 [默认: admin123456]: " admin_password admin_password=${admin_password:-admin123456} # 安装端口检测工具 install_port_detection_tools install_docker install_docker_compose mkdir -p "$INSTALL_DIR" mkdir -p "$TEMP_DIR" cd "$TEMP_DIR" if [ "$install_type" = "1" ]; then # 使用预编译版本 log_info "下载预编译面板..." local PANEL_URL="https://download.sharon.wiki/server/dashgo-server-linux-${ARCH}" log_info "下载地址: $PANEL_URL" if wget --show-progress -O "$INSTALL_DIR/dashgo-server" "$PANEL_URL" 2>&1; then chmod +x "$INSTALL_DIR/dashgo-server" log_success "预编译面板下载完成" else log_warn "下载预编译版本失败 (HTTP错误或网络问题)" log_info "切换到源码构建..." rm -f "$INSTALL_DIR/dashgo-server" 2>/dev/null install_type="2" fi if [ "$install_type" = "1" ]; then # 下载前端文件 log_info "下载前端文件..." mkdir -p "$INSTALL_DIR/web/dist" cd "$TEMP_DIR" # 下载 index.html if wget --show-progress -O "$INSTALL_DIR/web/dist/index.html" "https://download.sharon.wiki/web/dist/index.html" 2>&1; then log_success "前端 index.html 下载完成" else log_warn "前端 index.html 下载失败" fi # 下载 assets 目录(打包为 tar.gz) if wget --show-progress -O assets.tar.gz "https://download.sharon.wiki/web/dist/assets.tar.gz" 2>&1; then tar -xzf assets.tar.gz -C "$INSTALL_DIR/web/dist/" 2>/dev/null && log_success "前端 assets 下载完成" || { log_warn "assets 解压失败,尝试直接下载..." # 如果 tar.gz 不存在,尝试下载整个 dist 目录的 zip rm -f assets.tar.gz if wget --show-progress -O dist.zip "https://download.sharon.wiki/web/dist.zip" 2>&1; then unzip -q dist.zip -d "$INSTALL_DIR/web/" && log_success "前端文件下载完成" || log_warn "前端文件解压失败" else log_warn "前端文件下载失败,将使用空前端" fi } else log_warn "前端 assets 下载失败" fi # 下载配置模板 log_info "下载配置模板..." local REPO_URL="${GH_PROXY}https://github.com/${GITHUB_REPO}/archive/refs/heads/main.zip" # 尝试使用代理下载 if ! wget --show-progress -O dashgo.zip "$REPO_URL" 2>&1; then if [ -n "$GH_PROXY" ]; then log_warn "代理下载失败,切换到 GitHub 原源..." REPO_URL="https://github.com/${GITHUB_REPO}/archive/refs/heads/main.zip" rm -f dashgo.zip 2>/dev/null wget --show-progress -O dashgo.zip "$REPO_URL" 2>&1 || { log_warn "配置模板下载失败,将使用默认配置" mkdir -p "$INSTALL_DIR/configs" continue_without_template=true } fi fi if [ "$continue_without_template" != "true" ]; then log_info "解压配置模板..." unzip -q dashgo.zip # 自动检测解压后的目录 local extracted_dir=$(ls -d *-main 2>/dev/null | head -1) if [ -n "$extracted_dir" ]; then log_info "检测到解压目录: $extracted_dir" fi if [ -n "$extracted_dir" ] && [ -d "$extracted_dir" ]; then cp -r "$extracted_dir/configs" "$INSTALL_DIR/" 2>/dev/null || mkdir -p "$INSTALL_DIR/configs" cp "$extracted_dir/docker-compose.yaml" "$INSTALL_DIR/" 2>/dev/null || true cp "$extracted_dir/Dockerfile" "$INSTALL_DIR/" 2>/dev/null || true log_success "配置模板下载完成 (从 $extracted_dir)" else log_warn "未找到解压目录 (*-main)" mkdir -p "$INSTALL_DIR/configs" mkdir -p "$INSTALL_DIR/web/dist" fi fi fi fi if [ "$install_type" = "2" ]; then # 从源码构建 log_info "从源码构建..." # 询问是否构建前端 read -p "是否需要构建前端? (需要 Node.js) [Y/n]: " build_fe build_fe=${build_fe:-Y} if [ "$build_fe" = "Y" ] || [ "$build_fe" = "y" ]; then install_nodejs fi # 下载源码 local REPO_URL="${GH_PROXY}https://github.com/${GITHUB_REPO}/archive/refs/heads/main.zip" log_info "下载源码..." log_info "下载地址: $REPO_URL" cd "$TEMP_DIR" # 尝试使用代理下载 if ! wget --show-progress -O dashgo.zip "$REPO_URL" 2>&1; then if [ -n "$GH_PROXY" ]; then log_warn "代理下载失败,切换到 GitHub 原源..." REPO_URL="https://github.com/${GITHUB_REPO}/archive/refs/heads/main.zip" log_info "新下载地址: $REPO_URL" rm -f dashgo.zip 2>/dev/null if ! wget --show-progress -O dashgo.zip "$REPO_URL" 2>&1; then log_error "源码下载失败,请检查网络连接" exit 1 fi else log_error "源码下载失败,请检查网络连接" exit 1 fi fi if [ -f dashgo.zip ]; then log_info "解压源码..." unzip -q dashgo.zip # 自动检测解压后的目录 local extracted_dir=$(ls -d *-main 2>/dev/null | head -1) log_info "检测到的目录: $extracted_dir" if [ -n "$extracted_dir" ] && [ -d "$extracted_dir" ]; then cp -r "$extracted_dir"/* "$INSTALL_DIR/" log_success "源码下载完成 (从 $extracted_dir)" else log_error "未找到解压目录 (*-main)" exit 1 fi else log_error "源码下载失败,请检查网络连接" exit 1 fi fi cd "$INSTALL_DIR" # 处理前端构建 (仅源码构建时) if [ "$install_type" = "2" ]; then if [ "$build_fe" = "Y" ] || [ "$build_fe" = "y" ]; then if [ -d "web" ]; then build_frontend "$INSTALL_DIR/web" else log_warn "未找到 web 目录,跳过前端构建" fi else log_info "跳过前端构建" log_hint "如需前端,请手动构建: cd $INSTALL_DIR/web && npm install && npm run build" fi fi # 创建必要目录 mkdir -p data mkdir -p web/dist mkdir -p configs # 根据数据库类型设置参数 local use_mysql="false" if [ "$db_type" = "2" ]; then use_mysql="true" fi # 创建配置文件(总是创建,确保存在) create_panel_config "$use_mysql" # 调试:检查预编译文件 if [ -f "dashgo-server" ]; then log_info "检测到预编译文件: dashgo-server" ls -lh dashgo-server else log_info "未检测到预编译文件,将使用 Dockerfile 构建" fi # 创建 Docker Compose 文件 create_docker_compose "$use_mysql" "$web_port" # 处理 SSL 证书 if [ "$use_ssl" = "true" ]; then setup_ssl_certificate fi # 创建 Nginx 配置 create_nginx_config "$use_ssl" # 修复 Redis 内存警告 log_info "优化系统配置..." if ! grep -q "vm.overcommit_memory" /etc/sysctl.conf 2>/dev/null; then echo "vm.overcommit_memory = 1" | tee -a /etc/sysctl.conf >/dev/null sysctl vm.overcommit_memory=1 >/dev/null 2>&1 log_info "已启用内存 overcommit" fi # 清理旧容器(如果存在) log_info "清理旧容器..." docker compose down --remove-orphans >/dev/null 2>&1 || true # 启动服务 log_info "启动面板服务..." docker compose up -d --build # 等待服务启动 log_info "等待服务启动..." sleep 10 # 初始化数据库(确保表已创建) log_info "初始化数据库..." if [ "$db_type" = "1" ]; then # SQLite: 确保数据目录权限正确 chmod 755 data 2>/dev/null || true chmod 644 data/dashgo.db 2>/dev/null || true fi # 等待数据库初始化完成 sleep 5 # 检查数据库表是否创建 log_info "检查数据库状态..." if docker compose logs dashgo 2>&1 | grep -q "no such table"; then log_warn "检测到数据库表未创建,尝试重启服务..." docker compose restart dashgo sleep 10 fi # 检查服务状态 if docker compose ps | grep -q "Up"; then log_success "面板安装完成!" show_panel_info else log_error "服务启动失败,查看日志:" docker compose logs --tail=50 echo "" log_hint "尝试手动启动: cd $INSTALL_DIR && docker compose up -d" fi } # 创建面板配置 create_panel_config() { local use_mysql=${1:-false} log_info "创建配置文件..." # 生成随机密码 local DB_PASS=$(openssl rand -base64 16 | tr -dc 'a-zA-Z0-9' | head -c 16) local REDIS_PASS=$(openssl rand -base64 16 | tr -dc 'a-zA-Z0-9' | head -c 16) local JWT_SECRET=$(openssl rand -base64 32) local NODE_TOKEN=$(openssl rand -base64 32 | tr -dc 'a-zA-Z0-9' | head -c 32) # 创建 configs 目录 mkdir -p configs if [ "$use_mysql" = "true" ]; then # MySQL 配置(使用外部数据库) cat > configs/config.yaml << EOF # dashGO Configuration for Docker (External MySQL) app: name: "dashGO" mode: "release" listen: ":8080" database: driver: "mysql" host: "${mysql_host}" port: ${mysql_port} username: "${mysql_user}" password: "${mysql_password}" database: "${mysql_database}" redis: host: "redis" port: 6379 password: "${REDIS_PASS}" db: 0 jwt: secret: "${JWT_SECRET}" expire_hour: 24 node: token: "${NODE_TOKEN}" push_interval: 60 pull_interval: 60 enable_sync: false mail: host: "smtp.example.com" port: 587 username: "" password: "" from_name: "dashGO" from_addr: "noreply@example.com" encryption: "tls" telegram: bot_token: "" chat_id: "" admin: email: "${admin_email}" password: "${admin_password}" EOF else # SQLite 配置 (默认) cat > configs/config.yaml << EOF # dashGO Configuration for Docker (SQLite) app: name: "dashGO" mode: "release" listen: ":8080" database: driver: "sqlite" dsn: "data/dashgo.db" redis: host: "redis" port: 6379 password: "${REDIS_PASS}" db: 0 jwt: secret: "${JWT_SECRET}" expire_hour: 24 node: token: "${NODE_TOKEN}" push_interval: 60 pull_interval: 60 enable_sync: false mail: host: "smtp.example.com" port: 587 username: "" password: "" from_name: "dashGO" from_addr: "noreply@example.com" encryption: "tls" telegram: bot_token: "" chat_id: "" admin: email: "${admin_email}" password: "${admin_password}" EOF fi # 保存密码到环境文件 cat > .env << EOF WEB_PORT=${web_port} REDIS_PASSWORD=${REDIS_PASS} JWT_SECRET=${JWT_SECRET} NODE_TOKEN=${NODE_TOKEN} EOF log_info "配置文件已创建: configs/config.yaml" if [ "$use_mysql" = "true" ]; then log_hint "数据库类型: MySQL (外部)" log_hint "数据库地址: ${mysql_host}:${mysql_port}" log_hint "数据库名称: ${mysql_database}" else log_hint "数据库类型: SQLite (data/dashgo.db)" fi log_hint "Redis 密码: ${REDIS_PASS}" log_hint "节点 Token: ${NODE_TOKEN}" } # 创建 Docker Compose 文件 create_docker_compose() { local use_mysql=${1:-false} local web_port=${2:-80} # 检查是否存在预编译二进制文件 if [ -f "dashgo-server" ]; then # 为预编译版本创建简单的 Dockerfile(使用 Debian 基础镜像支持 glibc) cat > Dockerfile << 'EOF' FROM debian:bookworm-slim # 安装必要的运行时依赖 RUN apt-get update && \ apt-get install -y --no-install-recommends ca-certificates tzdata && \ rm -rf /var/lib/apt/lists/* WORKDIR /app # 复制预编译二进制文件 COPY ./dashgo-server /app/dashgo-server RUN chmod +x /app/dashgo-server # 创建必要的目录 RUN mkdir -p /app/configs /app/web/dist /app/data # 设置时区 ENV TZ=Asia/Shanghai EXPOSE 8080 CMD ["/app/dashgo-server", "-config", "/app/configs/config.yaml"] EOF log_info "已创建预编译版本的 Dockerfile (Debian 基础镜像)" fi if [ "$use_mysql" = "true" ]; then # MySQL 版本的 Docker Compose(使用外部 MySQL) cat > docker-compose.yaml << 'EOF' services: dashgo: build: . container_name: dashgo ports: - "8080:8080" volumes: - ./configs/config.yaml:/app/configs/config.yaml - ./data:/app/data - ./web/dist:/app/web/dist depends_on: - redis restart: unless-stopped environment: - TZ=Asia/Shanghai networks: - dashgo-net redis: image: redis:7-alpine container_name: dashgo-redis command: redis-server --requirepass ${REDIS_PASSWORD:-} volumes: - redis_data:/data ports: - "6379:6379" restart: unless-stopped networks: - dashgo-net nginx: image: nginx:alpine container_name: dashgo-nginx ports: - "${WEB_PORT}:80" - "443:443" volumes: - ./nginx.conf:/etc/nginx/nginx.conf - ./ssl:/etc/nginx/ssl depends_on: - dashgo restart: unless-stopped networks: - dashgo-net volumes: redis_data: networks: dashgo-net: driver: bridge EOF else # SQLite 版本的 Docker Compose (默认,更简单) cat > docker-compose.yaml << 'EOF' services: dashgo: build: . container_name: dashgo ports: - "8080:8080" volumes: - ./configs/config.yaml:/app/configs/config.yaml - ./data:/app/data - ./web/dist:/app/web/dist depends_on: - redis restart: unless-stopped environment: - TZ=Asia/Shanghai networks: - dashgo-net redis: image: redis:7-alpine container_name: dashgo-redis command: redis-server --requirepass ${REDIS_PASSWORD:-} volumes: - redis_data:/data ports: - "6379:6379" restart: unless-stopped networks: - dashgo-net nginx: image: nginx:alpine container_name: dashgo-nginx ports: - "${WEB_PORT}:80" - "443:443" volumes: - ./nginx.conf:/etc/nginx/nginx.conf - ./ssl:/etc/nginx/ssl depends_on: - dashgo restart: unless-stopped networks: - dashgo-net volumes: redis_data: networks: dashgo-net: driver: bridge EOF fi } # 创建初始化 SQL create_init_sql() { cat > init.sql << 'EOF' -- dashGO 初始化 SQL SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; -- 创建管理员账户 (密码: admin123) INSERT INTO `users` (`email`, `password`, `is_admin`, `is_staff`, `balance`, `created_at`, `updated_at`) VALUES ('admin@dashgo.local', '$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy', 1, 1, 0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()) ON DUPLICATE KEY UPDATE `is_admin` = 1; SET FOREIGN_KEY_CHECKS = 1; EOF } # 设置 SSL 证书 setup_ssl_certificate() { mkdir -p ssl case $cert_type in 1) # Cloudflare Origin Certificate log_info "请按以下步骤获取 Cloudflare Origin Certificate:" echo "" echo "1. 登录 Cloudflare 控制台" echo "2. 选择你的域名" echo "3. 进入 SSL/TLS → Origin Server" echo "4. 点击 'Create Certificate'" echo "5. 保持默认设置,点击 'Create'" echo "6. 复制证书内容" echo "" read -p "按回车继续,准备粘贴证书..." echo "" echo "请粘贴 Origin Certificate (以 -----BEGIN CERTIFICATE----- 开头):" echo "粘贴完成后按 Ctrl+D:" cat > ssl/cert.pem echo "" echo "请粘贴 Private Key (以 -----BEGIN PRIVATE KEY----- 开头):" echo "粘贴完成后按 Ctrl+D:" cat > ssl/key.pem log_success "Cloudflare Origin Certificate 已保存" log_hint "请在 Cloudflare 设置 SSL/TLS 模式为 'Full (strict)'" ;; 2) # 自签名证书 log_info "生成自签名证书..." read -p "请输入域名: " domain_name openssl req -x509 -nodes -days 365 -newkey rsa:2048 \ -keyout ssl/key.pem \ -out ssl/cert.pem \ -subj "/CN=${domain_name}" 2>/dev/null log_success "自签名证书已生成" log_warn "浏览器会显示不安全警告,仅用于测试" log_hint "请在 Cloudflare 设置 SSL/TLS 模式为 'Full' (不是 strict)" ;; 3) # 已有证书 log_info "请将证书文件放置到以下位置:" echo " 证书文件: $INSTALL_DIR/ssl/cert.pem" echo " 私钥文件: $INSTALL_DIR/ssl/key.pem" read -p "文件已放置好?按回车继续..." if [ ! -f "ssl/cert.pem" ] || [ ! -f "ssl/key.pem" ]; then log_error "未找到证书文件" exit 1 fi log_success "证书文件已确认" ;; esac # 设置权限 chmod 600 ssl/key.pem chmod 644 ssl/cert.pem } # 创建 Nginx 配置 create_nginx_config() { local use_ssl=${1:-false} if [ "$use_ssl" = "true" ]; then # HTTPS 配置 cat > nginx.conf << 'EOF' events { worker_connections 1024; } http { include /etc/nginx/mime.types; default_type application/octet-stream; # 日志格式(包含真实 IP) log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; access_log /var/log/nginx/access.log main; error_log /var/log/nginx/error.log warn; # 真实 IP 设置(支持 CDN) set_real_ip_from 0.0.0.0/0; real_ip_header X-Forwarded-For; real_ip_recursive on; sendfile on; keepalive_timeout 65; client_max_body_size 50m; # Gzip gzip on; gzip_vary on; gzip_min_length 1024; gzip_types text/plain text/css application/json application/javascript text/xml application/xml; upstream dashgo { server dashgo:8080; } # HTTP 重定向到 HTTPS server { listen 80 default_server; listen [::]:80 default_server; server_name _; return 301 https://$host$request_uri; } # HTTPS 服务器 server { listen 443 ssl http2 default_server; listen [::]:443 ssl http2 default_server; server_name _; # SSL 证书 ssl_certificate /etc/nginx/ssl/cert.pem; ssl_certificate_key /etc/nginx/ssl/key.pem; # SSL 配置 ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384; ssl_prefer_server_ciphers off; ssl_session_cache shared:SSL:10m; ssl_session_timeout 10m; # 增加缓冲区大小 client_header_buffer_size 4k; large_client_header_buffers 4 16k; # 错误页面 error_page 502 503 504 /50x.html; location = /50x.html { return 503 '{"error": "Service temporarily unavailable"}'; add_header Content-Type application/json; } location / { proxy_pass http://dashgo; proxy_http_version 1.1; # WebSocket 支持 proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; # 正确传递请求头(支持 CDN) proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $http_host; proxy_set_header X-Forwarded-Port $server_port; # 传递 CDN 相关头(如 Cloudflare) proxy_set_header CF-Connecting-IP $http_cf_connecting_ip; proxy_set_header CF-Ray $http_cf_ray; proxy_set_header CF-Visitor $http_cf_visitor; # 超时设置 proxy_connect_timeout 60s; proxy_send_timeout 60s; proxy_read_timeout 60s; # 缓冲设置 proxy_buffering on; proxy_buffer_size 4k; proxy_buffers 8 4k; proxy_busy_buffers_size 8k; # 错误处理 proxy_next_upstream error timeout invalid_header http_502 http_503 http_504; proxy_next_upstream_tries 1; } # 健康检查 location /health { access_log off; return 200 "healthy\n"; add_header Content-Type text/plain; } } } EOF else # HTTP 配置 cat > nginx.conf << 'EOF' events { worker_connections 1024; } http { include /etc/nginx/mime.types; default_type application/octet-stream; # 日志格式(包含真实 IP) log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; access_log /var/log/nginx/access.log main; error_log /var/log/nginx/error.log warn; # 真实 IP 设置(支持 CDN) set_real_ip_from 0.0.0.0/0; real_ip_header X-Forwarded-For; real_ip_recursive on; sendfile on; keepalive_timeout 65; client_max_body_size 50m; # Gzip gzip on; gzip_vary on; gzip_min_length 1024; gzip_types text/plain text/css application/json application/javascript text/xml application/xml; upstream dashgo { server dashgo:8080; } server { listen 80 default_server; listen [::]:80 default_server; server_name _; # 增加缓冲区大小,防止请求头过大 client_header_buffer_size 4k; large_client_header_buffers 4 16k; # 错误页面 error_page 502 503 504 /50x.html; location = /50x.html { return 503 '{"error": "Service temporarily unavailable"}'; add_header Content-Type application/json; } location / { proxy_pass http://dashgo; proxy_http_version 1.1; # WebSocket 支持 proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; # 正确传递请求头(支持 CDN) proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $http_host; proxy_set_header X-Forwarded-Port $server_port; # 传递 CDN 相关头(如 Cloudflare) proxy_set_header CF-Connecting-IP $http_cf_connecting_ip; proxy_set_header CF-Ray $http_cf_ray; proxy_set_header CF-Visitor $http_cf_visitor; # 超时设置 proxy_connect_timeout 60s; proxy_send_timeout 60s; proxy_read_timeout 60s; # 缓冲设置 proxy_buffering on; proxy_buffer_size 4k; proxy_buffers 8 4k; proxy_busy_buffers_size 8k; # 错误处理 proxy_next_upstream error timeout invalid_header http_502 http_503 http_504; proxy_next_upstream_tries 1; } # 健康检查 location /health { access_log off; return 200 "healthy\n"; add_header Content-Type text/plain; } } } EOF fi } # 显示面板信息 show_panel_info() { local IP=$(curl -s4 ip.sb 2>/dev/null || curl -s4 ifconfig.me 2>/dev/null || echo "YOUR_IP") echo "" echo "==========================================" echo -e "${GREEN}dashGO 面板安装完成!${NC}" echo "==========================================" echo "" echo "访问地址: http://${IP}:${web_port}" echo "后台地址: http://${IP}:${web_port}/admin" echo "" echo "管理员账户:" echo " 邮箱: ${admin_email}" echo " 密码: ${admin_password}" echo "" 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 restart" echo " 停止服务: cd $INSTALL_DIR && docker compose down" echo "" echo -e "${YELLOW}请及时修改默认密码!${NC}" echo "" } # ==================== Agent 安装 ==================== # 安装 sing-box install_singbox() { log_info "安装 sing-box..." if command -v sing-box &>/dev/null; then local current_version=$(sing-box version 2>/dev/null | head -1 | awk '{print $3}') log_info "sing-box 已安装,版本: $current_version" return 0 fi mkdir -p "$TEMP_DIR" cd "$TEMP_DIR" local SINGBOX_VERSION="$SINGBOX_DEFAULT_VERSION" local SINGBOX_URL="${GH_PROXY}https://github.com/SagerNet/sing-box/releases/download/v${SINGBOX_VERSION}/sing-box-${SINGBOX_VERSION}-linux-${SINGBOX_ARCH}.tar.gz" log_info "下载 sing-box v${SINGBOX_VERSION}..." if ! wget -q --show-progress -O sing-box.tar.gz "$SINGBOX_URL"; then log_error "下载 sing-box 失败" exit 1 fi tar -xzf sing-box.tar.gz mv sing-box-*/sing-box /usr/local/bin/ chmod +x /usr/local/bin/sing-box mkdir -p "$SINGBOX_DIR" mkdir -p "$SINGBOX_DIR/conf" # 创建默认配置 if [ ! -f "$SINGBOX_DIR/config.json" ]; then cat > "$SINGBOX_DIR/config.json" << 'EOF' { "log": { "level": "info", "timestamp": true }, "inbounds": [], "outbounds": [ { "type": "direct", "tag": "direct" } ] } EOF fi # 创建 systemd 服务 cat > /etc/systemd/system/sing-box.service << 'EOF' [Unit] Description=sing-box service Documentation=https://sing-box.sagernet.org After=network.target nss-lookup.target [Service] Type=simple ExecStart=/usr/local/bin/sing-box run -c /etc/sing-box/config.json Restart=on-failure RestartSec=10 LimitNOFILE=infinity [Install] WantedBy=multi-user.target EOF systemctl daemon-reload log_info "sing-box v${SINGBOX_VERSION} 安装完成" } # 交互式安装 Agent install_agent_interactive() { echo "" read -p "请输入面板地址 (如 https://your-panel.com): " PANEL_URL read -p "请输入节点 Token: " TOKEN if [ -z "$PANEL_URL" ] || [ -z "$TOKEN" ]; then log_error "面板地址和 Token 不能为空" exit 1 fi PANEL_URL="${PANEL_URL%/}" install_agent "$PANEL_URL" "$TOKEN" } # 安装 Agent install_agent() { local panel_url="${1:-$PANEL_URL}" local token="${2:-$TOKEN}" log_info "开始安装 dashGO Agent..." # 检测 Alpine Linux 环境 local is_alpine=false if [ "$OS" = "alpine" ]; then is_alpine=true log_info "检测到 Alpine Linux 环境" # 询问是否安装调试版本 echo "" echo "检测到 Alpine Linux 系统,建议使用调试版本以获得更好的故障排除支持" echo "" read -p "是否安装调试版本? [Y/n]: " install_debug install_debug=${install_debug:-Y} fi install_singbox mkdir -p "$AGENT_DIR" mkdir -p "$TEMP_DIR" cd "$TEMP_DIR" # 根据选择下载对应版本 local agent_binary="dashgo-agent" if [ "$is_alpine" = true ] && { [ "$install_debug" = "Y" ] || [ "$install_debug" = "y" ]; }; then agent_binary="dashgo-agent-debug" log_info "下载 Alpine 调试版本..." # 下载调试版本 local AGENT_DEBUG_URL="https://download.sharon.wiki/agent/dashgo-agent-debug-linux-${ARCH}" log_info "下载地址: $AGENT_DEBUG_URL" if wget --show-progress -O "$AGENT_DIR/dashgo-agent-debug" "$AGENT_DEBUG_URL" 2>&1; then log_success "调试版本 Agent 下载完成" chmod +x "$AGENT_DIR/dashgo-agent-debug" # 同时下载诊断脚本 log_info "下载诊断脚本..." if wget --show-progress -O "$AGENT_DIR/debug-alpine.sh" "https://download.sharon.wiki/agent/debug-alpine.sh" 2>&1; then chmod +x "$AGENT_DIR/debug-alpine.sh" log_success "诊断脚本下载完成" else log_warn "诊断脚本下载失败,将从源码获取" fi # 安装 Alpine 特定依赖 log_info "安装 Alpine 调试工具..." install_alpine_debug_tools # 运行启动诊断 log_info "运行启动诊断..." if [ -f "$AGENT_DIR/debug-alpine.sh" ]; then "$AGENT_DIR/debug-alpine.sh" > "$AGENT_DIR/diagnostic-report.txt" 2>&1 || true log_info "诊断报告已保存到: $AGENT_DIR/diagnostic-report.txt" fi else log_warn "调试版本下载失败,尝试从源码构建调试版本" build_agent_from_source "true" if [ -f "$AGENT_DIR/dashgo-agent-debug" ]; then log_success "调试版本从源码构建完成" else log_warn "调试版本构建失败,切换到标准版本" agent_binary="dashgo-agent" install_debug="n" fi fi fi # 如果不是调试版本或调试版本下载失败,下载标准版本 if [ "$agent_binary" = "dashgo-agent" ]; then local AGENT_URL="https://download.sharon.wiki/agent/dashgo-agent-linux-${ARCH}" log_info "下载 Agent..." log_info "下载地址: $AGENT_URL" if wget --show-progress -O "$AGENT_DIR/dashgo-agent" "$AGENT_URL" 2>&1; then log_success "Agent 下载完成" else log_warn "下载预编译版本失败 (HTTP错误或网络问题)" log_info "尝试从源码构建..." rm -f "$AGENT_DIR/dashgo-agent" 2>/dev/null build_agent_from_source "false" fi chmod +x "$AGENT_DIR/dashgo-agent" fi # 创建服务 create_agent_service "$panel_url" "$token" "$agent_binary" # 安装后验证 if [ "$is_alpine" = true ] && [ "$agent_binary" = "dashgo-agent-debug" ]; then run_post_install_verification "$agent_binary" fi log_success "Agent 安装完成!" # 显示 Alpine 特定提示 if [ "$is_alpine" = true ]; then show_alpine_agent_info "$agent_binary" else show_agent_info fi } # 从源码构建 Agent build_agent_from_source() { local build_debug="${1:-false}" if ! command -v go &>/dev/null; then log_info "安装 Go..." local GO_VERSION="1.21.5" local GO_URL="https://go.dev/dl/go${GO_VERSION}.linux-${ARCH}.tar.gz" cd "$TEMP_DIR" wget -q -O go.tar.gz "$GO_URL" rm -rf /usr/local/go tar -C /usr/local -xzf go.tar.gz export PATH=$PATH:/usr/local/go/bin export GOPATH=/root/go fi cd "$TEMP_DIR" git clone --depth 1 "${GH_PROXY}https://github.com/${GITHUB_REPO}.git" dashgo 2>/dev/null || \ git clone --depth 1 "https://github.com/${GITHUB_REPO}.git" dashgo cd dashgo/agent if [ "$build_debug" = "true" ]; then log_info "从源码构建调试版本..." # 构建调试版本,包含所有调试组件 go build -ldflags="-s -w" -o "$AGENT_DIR/dashgo-agent-debug" \ main_debug.go debug_logger.go alpine_types.go alpine_system_checker.go \ alpine_system_checker_unix.go alpine_error_handler.go diagnostic_tool.go version.go # 复制诊断脚本 if [ -f "debug-alpine.sh" ]; then cp debug-alpine.sh "$AGENT_DIR/" chmod +x "$AGENT_DIR/debug-alpine.sh" fi log_info "调试版本从源码构建完成" else log_info "从源码构建标准版本..." go build -ldflags="-s -w" -o "$AGENT_DIR/dashgo-agent" . log_info "标准版本从源码构建完成" fi } # 创建 Agent 服务 create_agent_service() { local panel_url="$1" local token="$2" local agent_binary="${3:-dashgo-agent}" log_info "创建 Agent 服务..." # 根据二进制文件名设置服务参数 local exec_start="${AGENT_DIR}/${agent_binary} -panel ${panel_url} -token ${token}" local service_description="dashGO Agent" if [ "$agent_binary" = "dashgo-agent-debug" ]; then service_description="dashGO Agent (Debug)" # 调试版本启用详细日志 exec_start="${exec_start} -debug" # 设置调试环境变量 cat > /etc/systemd/system/dashgo-agent.service << EOF [Unit] Description=${service_description} Documentation=https://github.com/${GITHUB_REPO} After=network.target sing-box.service [Service] Type=simple ExecStart=${exec_start} Restart=always RestartSec=5 StandardOutput=journal StandardError=journal LimitNOFILE=infinity Environment=DEBUG=1 [Install] WantedBy=multi-user.target EOF else cat > /etc/systemd/system/dashgo-agent.service << EOF [Unit] Description=${service_description} Documentation=https://github.com/${GITHUB_REPO} After=network.target sing-box.service [Service] Type=simple ExecStart=${exec_start} Restart=always RestartSec=5 StandardOutput=journal StandardError=journal LimitNOFILE=infinity [Install] WantedBy=multi-user.target EOF fi systemctl daemon-reload systemctl enable dashgo-agent systemctl start dashgo-agent log_info "Agent 服务已启动" } # 显示 Agent 信息 show_agent_info() { echo "" echo "==========================================" echo -e "${GREEN}dashGO Agent 安装完成!${NC}" echo "==========================================" echo "" echo "安装目录: $AGENT_DIR" echo "sing-box 目录: $SINGBOX_DIR" echo "" echo "常用命令:" echo " 查看 Agent 状态: systemctl status dashgo-agent" echo " 查看 Agent 日志: journalctl -u dashgo-agent -f" echo " 重启 Agent: systemctl restart dashgo-agent" echo " 查看 sing-box 状态: systemctl status sing-box" echo " 查看 sing-box 日志: journalctl -u sing-box -f" echo "" systemctl status dashgo-agent --no-pager 2>/dev/null || true } # 显示 Alpine Agent 信息 show_alpine_agent_info() { local agent_binary="${1:-dashgo-agent}" echo "" echo "==========================================" if [ "$agent_binary" = "dashgo-agent-debug" ]; then echo -e "${GREEN}dashGO Agent (Alpine 调试版) 安装完成!${NC}" else echo -e "${GREEN}dashGO Agent (Alpine 版) 安装完成!${NC}" fi echo "==========================================" echo "" echo "安装目录: $AGENT_DIR" echo "sing-box 目录: $SINGBOX_DIR" echo "系统类型: Alpine Linux $OS_VERSION" echo "" if [ "$agent_binary" = "dashgo-agent-debug" ]; then echo "调试功能:" echo " 诊断脚本: $AGENT_DIR/debug-alpine.sh" echo " 诊断报告: $AGENT_DIR/diagnostic-report.txt" echo " 运行诊断: $AGENT_DIR/dashgo-agent-debug diagnose" echo "" fi echo "常用命令:" echo " 查看 Agent 状态: systemctl status dashgo-agent" echo " 查看 Agent 日志: journalctl -u dashgo-agent -f" echo " 重启 Agent: systemctl restart dashgo-agent" echo " 查看 sing-box 状态: systemctl status sing-box" echo " 查看 sing-box 日志: journalctl -u sing-box -f" if [ "$agent_binary" = "dashgo-agent-debug" ]; then echo "" echo "调试命令:" echo " 运行完整诊断: $AGENT_DIR/debug-alpine.sh" echo " 运行快速诊断: $AGENT_DIR/dashgo-agent-debug diagnose" echo " 启用跟踪日志: TRACE=1 systemctl restart dashgo-agent" fi echo "" echo -e "${YELLOW}Alpine Linux 特别提示:${NC}" echo " - 如遇到兼容性问题,请确保已安装 gcompat: apk add gcompat" echo " - 网络问题可检查 DNS 配置: cat /etc/resolv.conf" echo " - 容器环境需要适当的权限和网络配置" if [ -f "$AGENT_DIR/diagnostic-report.txt" ]; then echo " - 启动诊断报告: $AGENT_DIR/diagnostic-report.txt" fi echo "" systemctl status dashgo-agent --no-pager 2>/dev/null || true } # 安装后验证 run_post_install_verification() { local agent_binary="${1:-dashgo-agent}" if [ "$agent_binary" != "dashgo-agent-debug" ]; then return 0 fi log_info "运行安装后验证..." # 验证调试版本功能 if [ -f "$AGENT_DIR/dashgo-agent-debug" ]; then log_info "验证调试版本功能..." # 测试帮助信息 if "$AGENT_DIR/dashgo-agent-debug" -h >/dev/null 2>&1; then log_success "调试版本可执行文件正常" else log_warn "调试版本可执行文件可能有问题" fi # 运行快速诊断 if [ -f "$AGENT_DIR/debug-alpine.sh" ]; then log_info "运行快速验证诊断..." timeout 30 "$AGENT_DIR/debug-alpine.sh" > "$AGENT_DIR/verification-report.txt" 2>&1 || true log_info "验证诊断报告已保存到: $AGENT_DIR/verification-report.txt" fi fi # 检查服务状态 sleep 3 if systemctl is-active dashgo-agent >/dev/null 2>&1; then log_success "Agent 服务运行正常" else log_warn "Agent 服务可能未正常启动,请检查日志" log_info "查看日志: journalctl -u dashgo-agent -f" fi # 检查依赖项 log_info "验证 Alpine 依赖项..." local missing_deps="" if ! command -v sing-box >/dev/null 2>&1; then missing_deps="$missing_deps sing-box" fi if [ "$OS" = "alpine" ]; then if ! apk info -e gcompat >/dev/null 2>&1 && [ ! -f /lib/libc.so.6 ]; then missing_deps="$missing_deps gcompat" fi if [ ! -d /etc/ssl/certs ] || [ -z "$(ls -A /etc/ssl/certs 2>/dev/null)" ]; then missing_deps="$missing_deps ca-certificates" fi fi if [ -n "$missing_deps" ]; then log_warn "缺少依赖项: $missing_deps" log_info "建议安装: apk add $missing_deps" else log_success "所有依赖项检查通过" fi } # ==================== 完整安装 ==================== # 完整安装 (面板 + Agent) install_all() { log_info "开始完整安装..." install_panel echo "" read -p "是否在本机同时安装 Agent? [y/N]: " install_local_agent if [ "$install_local_agent" = "y" ] || [ "$install_local_agent" = "Y" ]; then local IP=$(curl -s4 ip.sb 2>/dev/null || curl -s4 ifconfig.me 2>/dev/null || echo "localhost") echo "" log_hint "请先在面板后台添加节点并获取 Token" read -p "请输入节点 Token: " TOKEN if [ -n "$TOKEN" ]; then install_agent "http://${IP}:8080" "$TOKEN" else log_warn "未输入 Token,跳过 Agent 安装" log_hint "稍后可以运行: bash install.sh agent" fi fi log_success "安装完成!" } # ==================== 卸载功能 ==================== # 卸载面板 uninstall_panel() { log_info "卸载 dashGO 面板..." if [ -d "$INSTALL_DIR" ]; then cd "$INSTALL_DIR" docker compose down -v 2>/dev/null || true cd / read -p "是否删除数据目录? [y/N]: " delete_data if [ "$delete_data" = "y" ] || [ "$delete_data" = "Y" ]; then rm -rf "$INSTALL_DIR" log_info "数据目录已删除" else log_info "保留数据目录: $INSTALL_DIR" fi fi log_success "面板已卸载" } # 卸载 Agent uninstall_agent() { log_info "卸载 dashGO Agent..." systemctl stop dashgo-agent 2>/dev/null || true systemctl disable dashgo-agent 2>/dev/null || true rm -f /etc/systemd/system/dashgo-agent.service systemctl daemon-reload rm -rf "$AGENT_DIR" log_info "Agent 已卸载" read -p "是否同时卸载 sing-box? [y/N]: " uninstall_sb if [ "$uninstall_sb" = "y" ] || [ "$uninstall_sb" = "Y" ]; then systemctl stop sing-box 2>/dev/null || true systemctl disable sing-box 2>/dev/null || true rm -f /etc/systemd/system/sing-box.service systemctl daemon-reload rm -f /usr/local/bin/sing-box rm -rf "$SINGBOX_DIR" log_info "sing-box 已卸载" fi log_success "Agent 已卸载" } # ==================== 更新功能 ==================== # 更新面板 update_panel() { log_info "更新 dashGO 面板..." if [ ! -d "$INSTALL_DIR" ]; then log_error "面板未安装" exit 1 fi # 询问更新方式 echo "" echo "请选择更新方式:" echo " 1) 使用预编译版本 (推荐)" echo " 2) 从源码更新" echo "" read -p "请选择 [1-2]: " update_type update_type=${update_type:-1} cd "$INSTALL_DIR" # 备份配置和数据 log_info "备份配置文件..." cp configs/config.yaml configs/config.yaml.bak 2>/dev/null || cp config.yaml config.yaml.bak 2>/dev/null || true cp .env .env.bak 2>/dev/null || true # 备份前端构建产物 if [ -d "web/dist" ]; then log_info "备份前端构建产物..." mv web/dist web/dist.bak fi # 停止服务 log_info "停止服务..." docker compose down if [ "$update_type" = "1" ]; then # 使用预编译版本更新 log_info "下载预编译面板..." mkdir -p "$TEMP_DIR" cd "$TEMP_DIR" local PANEL_URL="https://download.sharon.wiki/server/dashgo-server-linux-${ARCH}" log_info "下载地址: $PANEL_URL" if wget --show-progress -O dashgo-server "$PANEL_URL" 2>&1; then # 备份旧版本 mv "$INSTALL_DIR/dashgo-server" "$INSTALL_DIR/dashgo-server.bak" 2>/dev/null || true # 安装新版本 mv dashgo-server "$INSTALL_DIR/dashgo-server" chmod +x "$INSTALL_DIR/dashgo-server" log_success "预编译面板更新完成" else log_warn "下载预编译版本失败 (HTTP错误或网络问题)" log_info "切换到源码更新..." rm -f dashgo-server 2>/dev/null update_type="2" fi fi if [ "$update_type" = "2" ]; then # 从源码更新 # 询问是否重新构建前端 read -p "是否重新构建前端? [y/N]: " rebuild_fe # 下载新版本 mkdir -p "$TEMP_DIR" cd "$TEMP_DIR" log_info "下载最新版本..." local REPO_URL="${GH_PROXY}https://github.com/${GITHUB_REPO}/archive/refs/heads/main.zip" log_info "下载地址: $REPO_URL" # 尝试使用代理下载 if ! wget --show-progress -O dashgo.zip "$REPO_URL" 2>&1; then if [ -n "$GH_PROXY" ]; then log_warn "代理下载失败,切换到 GitHub 原源..." REPO_URL="https://github.com/${GITHUB_REPO}/archive/refs/heads/main.zip" log_info "新下载地址: $REPO_URL" rm -f dashgo.zip 2>/dev/null if ! wget --show-progress -O dashgo.zip "$REPO_URL" 2>&1; then log_error "源码下载失败,请检查网络连接" exit 1 fi else log_error "源码下载失败,请检查网络连接" exit 1 fi fi if [ -f dashgo.zip ]; then log_info "解压源码..." unzip -q dashgo.zip # 自动检测解压后的目录 local extracted_dir=$(ls -d *-main 2>/dev/null | head -1) log_info "检测到的目录: $extracted_dir" if [ -n "$extracted_dir" ] && [ -d "$extracted_dir" ]; then log_success "源码下载完成 (从 $extracted_dir)" # 更新文件 (保留配置和数据) log_info "更新文件..." rsync -av --exclude='configs/config.yaml' --exclude='config.yaml' --exclude='.env' \ --exclude='data' --exclude='storage' --exclude='ssl' --exclude='web/dist' \ "$extracted_dir"/* "$INSTALL_DIR/" else log_error "未找到解压目录 (*-main)" exit 1 fi else log_error "源码下载失败,请检查网络连接" exit 1 fi cd "$INSTALL_DIR" # 处理前端 if [ "$rebuild_fe" = "y" ] || [ "$rebuild_fe" = "Y" ]; then log_info "重新构建前端..." install_nodejs if [ -d "web" ]; then build_frontend "$INSTALL_DIR/web" fi else # 恢复旧的前端构建 if [ -d "web/dist.bak" ]; then log_info "恢复旧的前端构建..." mv web/dist.bak web/dist else log_warn "未找到前端构建产物" fi fi fi cd "$INSTALL_DIR" # 恢复配置 log_info "恢复配置..." if [ -f "configs/config.yaml.bak" ]; then mv configs/config.yaml.bak configs/config.yaml elif [ -f "config.yaml.bak" ]; then mkdir -p configs mv config.yaml.bak configs/config.yaml fi mv .env.bak .env 2>/dev/null || true # 重新构建并启动 log_info "重新启动服务..." docker compose up -d --build # 等待服务启动 sleep 5 # 检查状态 if docker compose ps | grep -q "Up"; then log_success "面板更新完成!" else log_error "服务启动失败,查看日志:" docker compose logs --tail=30 fi } # 更新 Agent update_agent() { log_info "更新 dashGO Agent..." # 检测当前安装的版本 local current_binary="" local is_debug=false if [ -f "$AGENT_DIR/dashgo-agent-debug" ]; then current_binary="dashgo-agent-debug" is_debug=true log_info "检测到调试版本 Agent" elif [ -f "$AGENT_DIR/dashgo-agent" ]; then current_binary="dashgo-agent" log_info "检测到标准版本 Agent" else log_error "Agent 未安装" exit 1 fi # 停止服务 systemctl stop dashgo-agent # 下载新版本 mkdir -p "$TEMP_DIR" cd "$TEMP_DIR" if [ "$is_debug" = true ]; then # 更新调试版本 local AGENT_DEBUG_URL="https://download.sharon.wiki/agent/dashgo-agent-debug-linux-${ARCH}" log_info "下载调试版本地址: $AGENT_DEBUG_URL" if wget --show-progress -O "$AGENT_DIR/dashgo-agent-debug.new" "$AGENT_DEBUG_URL" 2>&1; then mv "$AGENT_DIR/dashgo-agent-debug.new" "$AGENT_DIR/dashgo-agent-debug" chmod +x "$AGENT_DIR/dashgo-agent-debug" log_success "调试版本 Agent 更新完成" # 同时更新诊断脚本 log_info "更新诊断脚本..." if wget --show-progress -O "$AGENT_DIR/debug-alpine.sh.new" "https://download.sharon.wiki/agent/debug-alpine.sh" 2>&1; then mv "$AGENT_DIR/debug-alpine.sh.new" "$AGENT_DIR/debug-alpine.sh" chmod +x "$AGENT_DIR/debug-alpine.sh" log_success "诊断脚本更新完成" else log_warn "诊断脚本更新失败" fi else log_warn "调试版本下载失败 (HTTP错误或网络问题)" log_info "尝试从源码构建..." rm -f "$AGENT_DIR/dashgo-agent-debug.new" 2>/dev/null build_agent_from_source "true" fi else # 更新标准版本 local AGENT_URL="https://download.sharon.wiki/agent/dashgo-agent-linux-${ARCH}" log_info "下载标准版本地址: $AGENT_URL" if wget --show-progress -O "$AGENT_DIR/dashgo-agent.new" "$AGENT_URL" 2>&1; then mv "$AGENT_DIR/dashgo-agent.new" "$AGENT_DIR/dashgo-agent" chmod +x "$AGENT_DIR/dashgo-agent" log_success "标准版本 Agent 更新完成" else log_warn "标准版本下载失败 (HTTP错误或网络问题)" log_info "尝试从源码构建..." rm -f "$AGENT_DIR/dashgo-agent.new" 2>/dev/null build_agent_from_source "false" fi fi # 重启服务 systemctl start dashgo-agent log_success "Agent 更新完成!" # 显示更新后的信息 if [ "$is_debug" = true ] && [ "$OS" = "alpine" ]; then echo "" log_info "调试版本特性:" echo " - 详细日志记录已启用" echo " - 可使用 $AGENT_DIR/debug-alpine.sh 运行诊断" echo " - 可使用 $AGENT_DIR/dashgo-agent-debug diagnose 快速诊断" fi } # ==================== 主函数 ==================== main() { show_banner check_root detect_arch detect_os check_cdn install_deps # 处理命令行参数 case "${1:-}" in panel) install_panel ;; agent) if [ -n "$2" ] && [ -n "$3" ]; then install_agent "$2" "$3" else install_agent_interactive fi ;; all) install_all ;; uninstall-panel) uninstall_panel ;; uninstall-agent) uninstall_agent ;; update-panel) update_panel ;; update-agent) update_agent ;; -h|--help) echo "用法: $0 [命令]" echo "" echo "命令:" echo " panel 安装面板" echo " agent [url] [token] 安装节点" echo " all 完整安装" echo " uninstall-panel 卸载面板" echo " uninstall-agent 卸载节点" echo " update-panel 更新面板" echo " update-agent 更新节点" echo "" echo "Alpine Linux 特性:" echo " - 自动检测 Alpine 环境" echo " - 提供调试版本选项" echo " - 安装 musl libc 兼容工具" echo " - 集成诊断脚本" echo " - 启动时自动诊断" echo "" echo "示例:" echo " $0 panel" echo " $0 agent https://panel.example.com abc123" echo " $0 all" echo "" echo "Alpine 调试:" echo " 在 Alpine 系统上安装 Agent 时会自动询问是否使用调试版本" echo " 调试版本包含详细日志、诊断工具和故障排除功能" ;; *) show_menu ;; esac } main "$@"