install.sh
Rama main · réplica de lectura del repositorio original.
#!/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 "$@"