340 lines
10 KiB
Bash
340 lines
10 KiB
Bash
#!/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}}
|
|
|
|
read -p "Enter Node Count [${NODE_COUNT:-1}]: " INPUT_COUNT
|
|
NODE_COUNT=${INPUT_COUNT:-${NODE_COUNT:-1}}
|
|
|
|
if ! [[ "$NODE_COUNT" =~ ^[0-9]+$ ]] || [[ "$NODE_COUNT" -lt 1 ]]; then
|
|
echo -e "${RED}Node Count must be a positive integer${NC}"
|
|
exit 1
|
|
fi
|
|
|
|
declare -a NODE_IDS
|
|
declare -a NODE_TAGS
|
|
|
|
for ((i=1; i<=NODE_COUNT; i++)); do
|
|
DEFAULT_NODE_ID=""
|
|
DEFAULT_NODE_TAG=""
|
|
if [[ "$i" -eq 1 && -n "$NODE_ID" ]]; then
|
|
DEFAULT_NODE_ID="$NODE_ID"
|
|
fi
|
|
read -p "Enter Node ID for node #$i [${DEFAULT_NODE_ID}]: " INPUT_ID
|
|
CURRENT_NODE_ID=${INPUT_ID:-$DEFAULT_NODE_ID}
|
|
if [[ -z "$CURRENT_NODE_ID" ]]; then
|
|
echo -e "${RED}Node ID is required for node #$i${NC}"
|
|
exit 1
|
|
fi
|
|
read -p "Enter Tag for node #$i (optional) [${DEFAULT_NODE_TAG}]: " INPUT_TAG
|
|
CURRENT_NODE_TAG=${INPUT_TAG:-$DEFAULT_NODE_TAG}
|
|
NODE_IDS+=("$CURRENT_NODE_ID")
|
|
NODE_TAGS+=("$CURRENT_NODE_TAG")
|
|
done
|
|
|
|
# Sync time (Critical for SS 2022)
|
|
echo -e "${YELLOW}Syncing system time...${NC}"
|
|
timedatectl set-ntp true || true
|
|
|
|
if [[ -z "$PANEL_URL" || -z "$PANEL_TOKEN" ]]; then
|
|
echo -e "${RED}All fields are required!${NC}"
|
|
exit 1
|
|
fi
|
|
|
|
# Clean up trailing slash
|
|
PANEL_URL="${PANEL_URL%/}"
|
|
CONFIG_PANEL_URL=$PANEL_URL
|
|
USER_PANEL_URL=$PANEL_URL
|
|
|
|
SERVICE_JSON=$(cat <<EOF
|
|
{
|
|
"type": "xboard",
|
|
"panel_url": "$PANEL_URL",
|
|
"config_panel_url": "$CONFIG_PANEL_URL",
|
|
"user_panel_url": "$USER_PANEL_URL",
|
|
"key": "$PANEL_TOKEN",
|
|
"sync_interval": "1m",
|
|
"report_interval": "1m"
|
|
EOF
|
|
)
|
|
|
|
if [[ "$NODE_COUNT" -eq 1 ]]; then
|
|
SERVICE_JSON+=$(cat <<EOF
|
|
,
|
|
"node_id": ${NODE_IDS[0]},
|
|
"config_node_id": ${NODE_IDS[0]},
|
|
"user_node_id": ${NODE_IDS[0]}
|
|
EOF
|
|
)
|
|
else
|
|
SERVICE_JSON+=$',
|
|
"nodes": ['
|
|
for ((i=0; i<NODE_COUNT; i++)); do
|
|
NODE_BLOCK=$(cat <<EOF
|
|
{
|
|
"node_id": ${NODE_IDS[$i]},
|
|
"config_node_id": ${NODE_IDS[$i]},
|
|
"user_node_id": ${NODE_IDS[$i]}
|
|
EOF
|
|
)
|
|
if [[ -n "${NODE_TAGS[$i]}" ]]; then
|
|
NODE_BLOCK+=$(cat <<EOF
|
|
,
|
|
"tag": "${NODE_TAGS[$i]}"
|
|
EOF
|
|
)
|
|
fi
|
|
NODE_BLOCK+=$'\n }'
|
|
if [[ "$i" -gt 0 ]]; then
|
|
SERVICE_JSON+=$','
|
|
fi
|
|
SERVICE_JSON+=$'\n'"$NODE_BLOCK"
|
|
done
|
|
SERVICE_JSON+=$'\n ]'
|
|
fi
|
|
SERVICE_JSON+=$'\n }'
|
|
|
|
# Generate Configuration
|
|
echo -e "${YELLOW}Generating configuration...${NC}"
|
|
cat > "$CONFIG_BASE_FILE" <<EOF
|
|
{
|
|
"log": {
|
|
"level": "info",
|
|
"timestamp": true
|
|
},
|
|
"experimental": {
|
|
"cache_file": {
|
|
"enabled": true,
|
|
"path": "$WORK_DIR/cache.db"
|
|
}
|
|
},
|
|
"dns": {
|
|
"servers": [
|
|
{
|
|
"tag": "dns-remote",
|
|
"type": "udp",
|
|
"server": "1.1.1.1",
|
|
"server_port": 53
|
|
}
|
|
]
|
|
},
|
|
"services": [
|
|
${SERVICE_JSON}
|
|
],
|
|
"inbounds": [],
|
|
"route": {
|
|
"rules": [
|
|
{
|
|
"protocol": "dns",
|
|
"action": "hijack-dns"
|
|
}
|
|
],
|
|
"auto_detect_interface": true
|
|
}
|
|
}
|
|
EOF
|
|
|
|
cat > "$CONFIG_OUTBOUNDS_FILE" <<EOF
|
|
{
|
|
"outbounds": [
|
|
{
|
|
"type": "direct",
|
|
"tag": "direct"
|
|
}
|
|
]
|
|
}
|
|
EOF
|
|
|
|
echo -e "${GREEN}Base configuration written to $CONFIG_BASE_FILE${NC}"
|
|
echo -e "${GREEN}Outbound configuration written to $CONFIG_OUTBOUNDS_FILE${NC}"
|
|
echo -e "${YELLOW}Edit $CONFIG_OUTBOUNDS_FILE when adding custom sing-box outbounds.${NC}"
|
|
|
|
if [[ "$ENABLE_PROXY_PROTOCOL_HINT" =~ ^([yY][eE][sS]|[yY]|1|true|TRUE)$ ]]; then
|
|
echo -e "${YELLOW}Proxy Protocol deployment hint enabled.${NC}"
|
|
echo -e "${YELLOW}To make real client IP reporting work, your panel node config response must include:${NC}"
|
|
echo -e "${YELLOW} \"accept_proxy_protocol\": true${NC}"
|
|
echo -e "${YELLOW}Only enable this when the upstream L4 proxy or load balancer actually sends PROXY protocol headers.${NC}"
|
|
echo -e "${YELLOW}If clients connect directly without a PROXY header, connections will fail after enabling it on the panel.${NC}"
|
|
else
|
|
echo -e "${YELLOW}Proxy Protocol is not expected for this deployment.${NC}"
|
|
echo -e "${YELLOW}Keep panel field \"accept_proxy_protocol\" disabled or absent unless you are using an L4 proxy/LB that sends it.${NC}"
|
|
fi
|
|
|
|
# Create Systemd Service
|
|
echo -e "${YELLOW}Creating systemd service...${NC}"
|
|
cat > "$SERVICE_FILE" <<EOF
|
|
[Unit]
|
|
Description=ganclient service
|
|
After=network.target nss-lookup.target
|
|
|
|
[Service]
|
|
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_NET_RAW
|
|
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_NET_RAW
|
|
ExecStart=$BINARY_PATH -D $WORK_DIR -C $CONFIG_MERGE_DIR run
|
|
Restart=on-failure
|
|
RestartSec=10
|
|
LimitNOFILE=infinity
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
EOF
|
|
|
|
# Reload and Start
|
|
systemctl daemon-reload
|
|
systemctl enable "$SERVICE_NAME"
|
|
systemctl restart "$SERVICE_NAME"
|
|
|
|
echo -e "${GREEN}Service installed and started successfully.${NC}"
|
|
echo -e "${GREEN}Check status with: systemctl status ${SERVICE_NAME}${NC}"
|
|
echo -e "${GREEN}View logs with: journalctl -u ${SERVICE_NAME} -f${NC}"
|
|
echo -e "${GREEN}Panel config endpoint must control PROXY protocol via accept_proxy_protocol when needed.${NC}"
|