#!/bin/bash # sing-box Xboard Integration Installation Script # This script automates the installation and configuration of sing-box with Xboard support. set -e # Colors RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' # Configuration CONFIG_DIR="/etc/sing-box" CONFIG_MERGE_DIR="$CONFIG_DIR/config.d" CONFIG_BASE_FILE="$CONFIG_MERGE_DIR/10-base.json" CONFIG_OUTBOUNDS_FILE="$CONFIG_MERGE_DIR/20-outbounds.json" WORK_DIR="/var/lib/sing-box" BINARY_PATH="/usr/local/bin/sing-box" SERVICE_NAME="ganclient" SERVICE_FILE="/etc/systemd/system/${SERVICE_NAME}.service" LEGACY_SERVICE_NAME="sing-box" LEGACY_SERVICE_FILE="/etc/systemd/system/${LEGACY_SERVICE_NAME}.service" echo -e "${GREEN}Welcome to ganclient Installation Script${NC}" # Check root if [[ $EUID -ne 0 ]]; then echo -e "${RED}This script must be run as root${NC}" exit 1 fi # Detect Architecture ARCH=$(uname -m) case $ARCH in x86_64) BINARY_ARCH="amd64" ;; aarch64) BINARY_ARCH="arm64" ;; *) echo -e "${RED}Unsupported architecture: $ARCH${NC}"; exit 1 ;; esac # Prepare directories mkdir -p "$CONFIG_DIR" mkdir -p "$CONFIG_MERGE_DIR" mkdir -p "$WORK_DIR" # Check and Install Go install_go() { echo -e "${YELLOW}Checking Go environment...${NC}" if command -v go >/dev/null 2>&1; then GO_VERSION=$(go version | awk '{print $3}' | sed 's/go//' | cut -d. -f1,2) if [[ "$(printf '%s\n' "1.24" "$GO_VERSION" | sort -V | head -n1)" == "1.24" ]]; then echo -e "${GREEN}Go $GO_VERSION already installed.${NC}" return fi fi echo -e "${YELLOW}Installing Go 1.24.7...${NC}" GO_TAR="go1.24.7.linux-$BINARY_ARCH.tar.gz" curl -L "https://golang.org/dl/$GO_TAR" -o "$GO_TAR" rm -rf /usr/local/go && tar -C /usr/local -xzf "$GO_TAR" rm "$GO_TAR" # Add to PATH for current session export PATH=$PATH:/usr/local/go/bin if ! grep -q "/usr/local/go/bin" ~/.bashrc; then echo 'export PATH=$PATH:/usr/local/go/bin' >> ~/.bashrc fi echo -e "${GREEN}Go installed successfully.${NC}" } # Build sing-box build_sing_box() { echo -e "${YELLOW}Building sing-box from source...${NC}" # Check if we are in the source directory if [[ ! -f "go.mod" ]]; then echo -e "${YELLOW}Source not found in current directory. Cloning repository...${NC}" if ! command -v git >/dev/null 2>&1; then echo -e "${YELLOW}Installing git...${NC}" apt-get update && apt-get install -y git || yum install -y git fi git clone https://github.com/sagernet/sing-box.git sing-box-src cd sing-box-src else echo -e "${GREEN}Found go.mod in current directory. Building from local source.${NC}" fi # Build params from Makefile VERSION=$(git rev-parse --short HEAD 2>/dev/null || echo "custom") # Reduced tags for safer build on smaller servers TAGS="with_quic,with_utls,with_clash_api,with_gvisor" echo -e "${YELLOW}Starting compilation (this may take a few minutes)...${NC}" echo -e "${YELLOW}Note: Using -p 1 to save memory and avoid silent crashes.${NC}" # Use -o to be explicit about output location # Use -p 1 to limit parallel builds (prevent OOM spikes) # Redirect stderr to stdout to see errors clearly if ! go build -v -p 1 -trimpath \ -o "$BINARY_PATH" \ -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=$VERSION' -s -w" \ -tags "$TAGS" \ ./cmd/sing-box 2>&1; then echo -e "${RED}Compilation process failed or was terminated.${NC}" echo -e "${YELLOW}Checking system status...${NC}" free -m exit 1 fi if [[ -f "$BINARY_PATH" ]]; then chmod +x "$BINARY_PATH" echo -e "${GREEN}sing-box compiled successfully and installed to $BINARY_PATH${NC}" else echo -e "${RED}Compilation failed: binary not found!${NC}" exit 1 fi } install_go build_sing_box cleanup_legacy_service() { echo -e "${YELLOW}Cleaning up legacy sing-box service if present...${NC}" if systemctl list-unit-files | grep -q "^${LEGACY_SERVICE_NAME}\.service"; then systemctl stop "${LEGACY_SERVICE_NAME}" 2>/dev/null || true systemctl disable "${LEGACY_SERVICE_NAME}" 2>/dev/null || true fi if [[ -f "$LEGACY_SERVICE_FILE" ]]; then rm -f "$LEGACY_SERVICE_FILE" fi if [[ -L "/etc/systemd/system/multi-user.target.wants/${LEGACY_SERVICE_NAME}.service" ]]; then rm -f "/etc/systemd/system/multi-user.target.wants/${LEGACY_SERVICE_NAME}.service" fi } cleanup_legacy_service # Load .env if exists if [[ -f ".env" ]]; then echo -e "${YELLOW}Loading configuration from .env...${NC}" source .env fi # Interactive Prompts read -p "Enter Panel URL [${PANEL_URL}]: " INPUT_URL PANEL_URL=${INPUT_URL:-$PANEL_URL} read -p "Enter Panel Token (Node Key) [${PANEL_TOKEN}]: " INPUT_TOKEN PANEL_TOKEN=${INPUT_TOKEN:-$PANEL_TOKEN} read -p "This node is behind an L4 proxy/LB that sends PROXY protocol? [${ENABLE_PROXY_PROTOCOL_HINT:-n}]: " INPUT_PROXY_PROTOCOL ENABLE_PROXY_PROTOCOL_HINT=${INPUT_PROXY_PROTOCOL:-${ENABLE_PROXY_PROTOCOL_HINT:-n}} declare -a NODE_IDS i=1 while true; do DEFAULT_NODE_ID="" if [[ "$i" -eq 1 && -n "$NODE_ID" ]]; then DEFAULT_NODE_ID="$NODE_ID" fi if [[ -n "$DEFAULT_NODE_ID" ]]; then read -p "Enter Node ID for node #$i [${DEFAULT_NODE_ID}] (type NO to finish): " INPUT_ID else read -p "Enter Node ID for node #$i (type NO to finish): " INPUT_ID fi CURRENT_NODE_ID=${INPUT_ID:-$DEFAULT_NODE_ID} if [[ "$CURRENT_NODE_ID" =~ ^([nN][oO])$ ]]; then if [[ "${#NODE_IDS[@]}" -eq 0 ]]; then echo -e "${RED}At least one Node ID is required${NC}" exit 1 fi break fi if [[ -z "$CURRENT_NODE_ID" ]]; then echo -e "${RED}Node ID is required for node #$i${NC}" exit 1 fi if ! [[ "$CURRENT_NODE_ID" =~ ^[0-9]+$ ]]; then echo -e "${RED}Node ID must be a positive integer${NC}" exit 1 fi NODE_IDS+=("$CURRENT_NODE_ID") ((i++)) done NODE_COUNT=${#NODE_IDS[@]} DNS_MODE_DEFAULT=${DNS_MODE:-udp} read -p "Enter DNS mode [${DNS_MODE_DEFAULT}] (udp/local): " INPUT_DNS_MODE DNS_MODE=$(echo "${INPUT_DNS_MODE:-$DNS_MODE_DEFAULT}" | tr '[:upper:]' '[:lower:]') case "$DNS_MODE" in udp) DNS_SERVER_DEFAULT=${DNS_SERVER:-1.1.1.1} DNS_SERVER_PORT_DEFAULT=${DNS_SERVER_PORT:-53} read -p "Enter DNS server [${DNS_SERVER_DEFAULT}]: " INPUT_DNS_SERVER DNS_SERVER=${INPUT_DNS_SERVER:-$DNS_SERVER_DEFAULT} read -p "Enter DNS server port [${DNS_SERVER_PORT_DEFAULT}]: " INPUT_DNS_SERVER_PORT DNS_SERVER_PORT=${INPUT_DNS_SERVER_PORT:-$DNS_SERVER_PORT_DEFAULT} if [[ -z "$DNS_SERVER" ]]; then echo -e "${RED}DNS server is required in udp mode${NC}" exit 1 fi if ! [[ "$DNS_SERVER_PORT" =~ ^[0-9]+$ ]] || [[ "$DNS_SERVER_PORT" -lt 1 ]] || [[ "$DNS_SERVER_PORT" -gt 65535 ]]; then echo -e "${RED}DNS server port must be an integer between 1 and 65535${NC}" exit 1 fi DNS_SERVER_JSON=$(cat < "$CONFIG_BASE_FILE" < "$CONFIG_OUTBOUNDS_FILE" < "$SERVICE_FILE" <