574 lines
17 KiB
Bash
574 lines
17 KiB
Bash
#!/bin/bash
|
|
|
|
set -e
|
|
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
NC='\033[0m'
|
|
|
|
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="singbox"
|
|
SERVICE_FILE="/etc/systemd/system/${SERVICE_NAME}.service"
|
|
LEGACY_SERVICE_NAMES=("ganclient" "sing-box")
|
|
RELEASE_BASE_URL="${RELEASE_BASE_URL:-https://s3.cloudyun.top/downloads/singbox}"
|
|
PUBLISHED_SCRIPT_URL="${PUBLISHED_SCRIPT_URL:-https://s3.cloudyun.top/downloads/singbox/install.sh}"
|
|
V2BX_DETECTED=0
|
|
V2BX_CONFIG_PATH=""
|
|
UNINSTALL_V2BX_DEFAULT="${UNINSTALL_V2BX_DEFAULT:-n}"
|
|
SCRIPT_VERSION="${SCRIPT_VERSION:-v1.2.4}"
|
|
declare -a V2BX_IMPORTED_NODE_IDS=()
|
|
|
|
echo -e "${GREEN}Welcome to singbox Release Installation Script${NC}"
|
|
echo -e "${GREEN}Script version: ${SCRIPT_VERSION}${NC}"
|
|
echo -e "${YELLOW}Published install script: ${PUBLISHED_SCRIPT_URL}${NC}"
|
|
|
|
if [[ $EUID -ne 0 ]]; then
|
|
echo -e "${RED}This script must be run as root${NC}"
|
|
exit 1
|
|
fi
|
|
|
|
OS="$(uname -s)"
|
|
if [[ "$OS" != "Linux" ]]; then
|
|
echo -e "${RED}This install script currently supports Linux only. Current OS: ${OS}${NC}"
|
|
exit 1
|
|
fi
|
|
|
|
if [[ ! -t 0 ]]; then
|
|
if [[ -r /dev/tty ]]; then
|
|
exec 3</dev/tty
|
|
else
|
|
echo -e "${RED}Interactive input requires a TTY. Please run this script in a terminal.${NC}"
|
|
exit 1
|
|
fi
|
|
else
|
|
exec 3<&0
|
|
fi
|
|
|
|
ARCH="$(uname -m)"
|
|
case "$ARCH" in
|
|
x86_64) BINARY_ARCH="amd64" ;;
|
|
aarch64|arm64) BINARY_ARCH="arm64" ;;
|
|
armv7l|armv7) BINARY_ARCH="armv7" ;;
|
|
*)
|
|
echo -e "${RED}Unsupported architecture: $ARCH${NC}"
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
DOWNLOAD_TARGET="${DOWNLOAD_TARGET:-linux-${BINARY_ARCH}}"
|
|
DOWNLOAD_URL="${DOWNLOAD_URL:-${RELEASE_BASE_URL}/sing-box-${DOWNLOAD_TARGET}}"
|
|
TMP_BINARY="$(mktemp)"
|
|
|
|
sanitize_value() {
|
|
printf '%s' "$1" | tr -d '\r' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//'
|
|
}
|
|
|
|
normalize_node_id_input() {
|
|
local value
|
|
value="$(sanitize_value "$1")"
|
|
value="${value#[}"
|
|
value="${value%]}"
|
|
value="${value//,/ }"
|
|
value="${value//;/ }"
|
|
value="${value//\"/}"
|
|
value="${value//\'/}"
|
|
value="$(printf '%s' "$value" | tr -s '[:space:]' ' ')"
|
|
sanitize_value "$value"
|
|
}
|
|
|
|
append_unique_node_id() {
|
|
local normalized_value
|
|
local node_id_part
|
|
local existing_node_id
|
|
|
|
normalized_value="$(normalize_node_id_input "$1")"
|
|
if [[ -z "$normalized_value" ]]; then
|
|
return 0
|
|
fi
|
|
|
|
read -r -a NORMALIZED_NODE_ID_PARTS <<< "$normalized_value"
|
|
for node_id_part in "${NORMALIZED_NODE_ID_PARTS[@]}"; do
|
|
if ! [[ "$node_id_part" =~ ^[0-9]+$ ]]; then
|
|
continue
|
|
fi
|
|
for existing_node_id in "${V2BX_IMPORTED_NODE_IDS[@]}"; do
|
|
if [[ "$existing_node_id" == "$node_id_part" ]]; then
|
|
node_id_part=""
|
|
break
|
|
fi
|
|
done
|
|
if [[ -n "$node_id_part" ]]; then
|
|
V2BX_IMPORTED_NODE_IDS+=("$node_id_part")
|
|
fi
|
|
done
|
|
}
|
|
|
|
find_v2bx_config() {
|
|
local candidate
|
|
for candidate in \
|
|
"/etc/V2bX/config.json" \
|
|
"/usr/local/V2bX/config.json" \
|
|
"/etc/v2bx/config.json" \
|
|
"/usr/local/etc/V2bX/config.json"
|
|
do
|
|
if [[ -f "$candidate" ]]; then
|
|
V2BX_CONFIG_PATH="$candidate"
|
|
return 0
|
|
fi
|
|
done
|
|
return 1
|
|
}
|
|
|
|
detect_v2bx() {
|
|
if command -v v2bx >/dev/null 2>&1; then
|
|
V2BX_DETECTED=1
|
|
fi
|
|
if find_v2bx_config; then
|
|
V2BX_DETECTED=1
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
load_v2bx_defaults() {
|
|
if [[ -z "$V2BX_CONFIG_PATH" ]] && ! find_v2bx_config; then
|
|
return 1
|
|
fi
|
|
|
|
echo -e "${YELLOW}Detected V2bX configuration: ${V2BX_CONFIG_PATH}${NC}"
|
|
V2BX_IMPORTED_NODE_IDS=()
|
|
|
|
if command -v python3 >/dev/null 2>&1; then
|
|
local parsed
|
|
parsed="$(python3 - "$V2BX_CONFIG_PATH" <<'PY'
|
|
import json
|
|
import sys
|
|
|
|
path = sys.argv[1]
|
|
with open(path, "r", encoding="utf-8") as f:
|
|
data = json.load(f)
|
|
|
|
nodes = data.get("Nodes") or []
|
|
node = nodes[0] if nodes else {}
|
|
|
|
api_host = node.get("ApiHost", "") if node else ""
|
|
api_key = node.get("ApiKey", "") if node else ""
|
|
print(f"API_HOST={api_host or ''}")
|
|
print(f"API_KEY={api_key or ''}")
|
|
for entry in nodes:
|
|
node_id = entry.get("NodeID", "")
|
|
if node_id is None:
|
|
node_id = ""
|
|
print(f"NODE_ID={node_id}")
|
|
PY
|
|
)"
|
|
if [[ -n "$parsed" ]]; then
|
|
local parsed_line
|
|
while IFS= read -r parsed_line; do
|
|
parsed_line="$(sanitize_value "$parsed_line")"
|
|
case "$parsed_line" in
|
|
API_HOST=*)
|
|
if [[ -z "${PANEL_URL:-}" ]]; then
|
|
PANEL_URL="$(sanitize_value "${parsed_line#API_HOST=}")"
|
|
fi
|
|
;;
|
|
API_KEY=*)
|
|
if [[ -z "${PANEL_TOKEN:-}" ]]; then
|
|
PANEL_TOKEN="$(sanitize_value "${parsed_line#API_KEY=}")"
|
|
fi
|
|
;;
|
|
NODE_ID=*)
|
|
append_unique_node_id "${parsed_line#NODE_ID=}"
|
|
;;
|
|
esac
|
|
done <<< "$parsed"
|
|
fi
|
|
elif command -v jq >/dev/null 2>&1; then
|
|
local parsed
|
|
parsed="$(jq -r '(.Nodes[0].ApiHost // "" | "API_HOST=" + .), (.Nodes[0].ApiKey // "" | "API_KEY=" + .), (.Nodes[]?.NodeID // "" | tostring | "NODE_ID=" + .)' "$V2BX_CONFIG_PATH" 2>/dev/null || true)"
|
|
if [[ -n "$parsed" ]]; then
|
|
local parsed_line
|
|
while IFS= read -r parsed_line; do
|
|
parsed_line="$(sanitize_value "$parsed_line")"
|
|
case "$parsed_line" in
|
|
API_HOST=*)
|
|
if [[ -z "${PANEL_URL:-}" ]]; then
|
|
PANEL_URL="$(sanitize_value "${parsed_line#API_HOST=}")"
|
|
fi
|
|
;;
|
|
API_KEY=*)
|
|
if [[ -z "${PANEL_TOKEN:-}" ]]; then
|
|
PANEL_TOKEN="$(sanitize_value "${parsed_line#API_KEY=}")"
|
|
fi
|
|
;;
|
|
NODE_ID=*)
|
|
append_unique_node_id "${parsed_line#NODE_ID=}"
|
|
;;
|
|
esac
|
|
done <<< "$parsed"
|
|
fi
|
|
else
|
|
echo -e "${YELLOW}Neither python3 nor jq found, skipping automatic V2bX config import.${NC}"
|
|
fi
|
|
|
|
if [[ -z "${NODE_ID:-}" && "${#V2BX_IMPORTED_NODE_IDS[@]}" -gt 0 ]]; then
|
|
NODE_ID="${V2BX_IMPORTED_NODE_IDS[0]}"
|
|
fi
|
|
|
|
if [[ "${#V2BX_IMPORTED_NODE_IDS[@]}" -gt 0 ]]; then
|
|
echo -e "${YELLOW}Imported defaults from V2bX config: ApiHost=${PANEL_URL:-<empty>}, NodeIDs=$(IFS=,; echo "${V2BX_IMPORTED_NODE_IDS[*]}")${NC}"
|
|
else
|
|
echo -e "${YELLOW}Imported defaults from V2bX config: ApiHost=${PANEL_URL:-<empty>}, NodeIDs=<empty>${NC}"
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
stop_v2bx_if_present() {
|
|
if [[ "$V2BX_DETECTED" -ne 1 ]]; then
|
|
return 0
|
|
fi
|
|
if ! command -v v2bx >/dev/null 2>&1; then
|
|
echo -e "${YELLOW}V2bX config detected but 'v2bx' command not found, skipping stop/disable.${NC}"
|
|
return 0
|
|
fi
|
|
|
|
echo -e "${YELLOW}Detected V2bX, stopping and disabling it before continuing...${NC}"
|
|
v2bx stop || true
|
|
v2bx disable || true
|
|
}
|
|
|
|
prompt_uninstall_v2bx() {
|
|
if [[ "$V2BX_DETECTED" -ne 1 ]]; then
|
|
return 0
|
|
fi
|
|
if ! command -v v2bx >/dev/null 2>&1; then
|
|
return 0
|
|
fi
|
|
|
|
read -u 3 -p "Detected V2bX. Uninstall it now? [${UNINSTALL_V2BX_DEFAULT}]: " INPUT_UNINSTALL_V2BX
|
|
local uninstall_v2bx_answer
|
|
uninstall_v2bx_answer=${INPUT_UNINSTALL_V2BX:-$UNINSTALL_V2BX_DEFAULT}
|
|
|
|
if [[ "$uninstall_v2bx_answer" =~ ^([yY][eE][sS]|[yY]|1|true|TRUE)$ ]]; then
|
|
echo -e "${YELLOW}Running: v2bx uninstall${NC}"
|
|
v2bx uninstall
|
|
else
|
|
echo -e "${YELLOW}Keeping existing V2bX installation.${NC}"
|
|
fi
|
|
}
|
|
|
|
download_binary() {
|
|
echo -e "${YELLOW}Downloading sing-box release binary...${NC}"
|
|
echo -e "${YELLOW}Target: ${DOWNLOAD_TARGET}${NC}"
|
|
echo -e "${YELLOW}URL: ${DOWNLOAD_URL}${NC}"
|
|
|
|
if command -v curl >/dev/null 2>&1; then
|
|
if ! curl -fL "${DOWNLOAD_URL}" -o "${TMP_BINARY}"; then
|
|
echo -e "${RED}Failed to download release binary with curl.${NC}"
|
|
rm -f "${TMP_BINARY}"
|
|
exit 1
|
|
fi
|
|
elif command -v wget >/dev/null 2>&1; then
|
|
if ! wget -O "${TMP_BINARY}" "${DOWNLOAD_URL}"; then
|
|
echo -e "${RED}Failed to download release binary with wget.${NC}"
|
|
rm -f "${TMP_BINARY}"
|
|
exit 1
|
|
fi
|
|
else
|
|
echo -e "${RED}Neither curl nor wget is installed.${NC}"
|
|
rm -f "${TMP_BINARY}"
|
|
exit 1
|
|
fi
|
|
|
|
install -m 0755 "${TMP_BINARY}" "${BINARY_PATH}"
|
|
rm -f "${TMP_BINARY}"
|
|
|
|
if [[ ! -x "${BINARY_PATH}" ]]; then
|
|
echo -e "${RED}Binary install failed: ${BINARY_PATH} not executable.${NC}"
|
|
exit 1
|
|
fi
|
|
|
|
echo -e "${GREEN}sing-box downloaded and installed to ${BINARY_PATH}${NC}"
|
|
}
|
|
|
|
cleanup_legacy_service() {
|
|
echo -e "${YELLOW}Cleaning up legacy services if present...${NC}"
|
|
for legacy_service_name in "${LEGACY_SERVICE_NAMES[@]}"; do
|
|
if [[ "$legacy_service_name" == "$SERVICE_NAME" ]]; then
|
|
continue
|
|
fi
|
|
legacy_service_file="/etc/systemd/system/${legacy_service_name}.service"
|
|
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
|
|
done
|
|
}
|
|
|
|
detect_v2bx
|
|
stop_v2bx_if_present
|
|
|
|
mkdir -p "$CONFIG_DIR"
|
|
mkdir -p "$CONFIG_MERGE_DIR"
|
|
mkdir -p "$WORK_DIR"
|
|
|
|
if [[ -f ".env" ]]; then
|
|
echo -e "${YELLOW}Loading configuration from .env...${NC}"
|
|
source .env
|
|
fi
|
|
|
|
load_v2bx_defaults || true
|
|
|
|
PANEL_URL="$(sanitize_value "${PANEL_URL:-}")"
|
|
PANEL_TOKEN="$(sanitize_value "${PANEL_TOKEN:-}")"
|
|
NODE_ID="$(sanitize_value "${NODE_ID:-}")"
|
|
ENABLE_PROXY_PROTOCOL_HINT="$(sanitize_value "${ENABLE_PROXY_PROTOCOL_HINT:-n}")"
|
|
|
|
download_binary
|
|
cleanup_legacy_service
|
|
|
|
read -u 3 -p "Enter Panel URL [${PANEL_URL}]: " INPUT_URL
|
|
PANEL_URL="$(sanitize_value "${INPUT_URL:-$PANEL_URL}")"
|
|
|
|
read -u 3 -p "Enter Panel Token (Node Key) [${PANEL_TOKEN}]: " INPUT_TOKEN
|
|
PANEL_TOKEN="$(sanitize_value "${INPUT_TOKEN:-$PANEL_TOKEN}")"
|
|
|
|
read -u 3 -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="$(sanitize_value "${INPUT_PROXY_PROTOCOL:-${ENABLE_PROXY_PROTOCOL_HINT:-n}}")"
|
|
|
|
declare -a NODE_IDS
|
|
|
|
i=1
|
|
while true; do
|
|
DEFAULT_NODE_ID=""
|
|
if [[ "$i" -le "${#V2BX_IMPORTED_NODE_IDS[@]}" ]]; then
|
|
DEFAULT_NODE_ID="${V2BX_IMPORTED_NODE_IDS[$((i-1))]}"
|
|
elif [[ "$i" -eq 1 && -n "${NODE_ID:-}" ]]; then
|
|
DEFAULT_NODE_ID="$NODE_ID"
|
|
fi
|
|
if [[ -n "$DEFAULT_NODE_ID" ]]; then
|
|
read -u 3 -p "Enter Node ID for node #$i [${DEFAULT_NODE_ID}] (type NO to finish): " INPUT_ID
|
|
else
|
|
read -u 3 -p "Enter Node ID for node #$i (type NO to finish): " INPUT_ID
|
|
fi
|
|
CURRENT_NODE_ID="$(sanitize_value "${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
|
|
CURRENT_NODE_ID="$(normalize_node_id_input "$CURRENT_NODE_ID")"
|
|
if [[ -z "$CURRENT_NODE_ID" ]]; then
|
|
echo -e "${RED}Node ID is required for node #$i${NC}"
|
|
exit 1
|
|
fi
|
|
read -r -a CURRENT_NODE_ID_PARTS <<< "$CURRENT_NODE_ID"
|
|
if [[ "${#CURRENT_NODE_ID_PARTS[@]}" -eq 0 ]]; then
|
|
echo -e "${RED}Node ID is required for node #$i${NC}"
|
|
exit 1
|
|
fi
|
|
for CURRENT_NODE_ID_PART in "${CURRENT_NODE_ID_PARTS[@]}"; do
|
|
if ! [[ "$CURRENT_NODE_ID_PART" =~ ^[0-9]+$ ]]; then
|
|
echo -e "${RED}Node ID must be a positive integer, got: ${CURRENT_NODE_ID_PART}${NC}"
|
|
exit 1
|
|
fi
|
|
NODE_IDS+=("$CURRENT_NODE_ID_PART")
|
|
done
|
|
((i++))
|
|
done
|
|
|
|
NODE_COUNT=${#NODE_IDS[@]}
|
|
|
|
DNS_MODE_DEFAULT=${DNS_MODE:-udp}
|
|
read -u 3 -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 -u 3 -p "Enter DNS server [${DNS_SERVER_DEFAULT}]: " INPUT_DNS_SERVER
|
|
DNS_SERVER=${INPUT_DNS_SERVER:-$DNS_SERVER_DEFAULT}
|
|
read -u 3 -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 <<EOF
|
|
{
|
|
"tag": "dns-upstream",
|
|
"type": "udp",
|
|
"server": "$DNS_SERVER",
|
|
"server_port": $DNS_SERVER_PORT
|
|
}
|
|
EOF
|
|
)
|
|
;;
|
|
local)
|
|
DNS_SERVER_JSON=$(cat <<EOF
|
|
{
|
|
"tag": "dns-local",
|
|
"type": "local"
|
|
}
|
|
EOF
|
|
)
|
|
;;
|
|
*)
|
|
echo -e "${RED}Unsupported DNS mode: $DNS_MODE. Supported values: udp, local${NC}"
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
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
|
|
|
|
PANEL_URL="${PANEL_URL%/}"
|
|
|
|
SERVICE_JSON=$(cat <<EOF
|
|
{
|
|
"type": "xboard",
|
|
"panel_url": "$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]}
|
|
EOF
|
|
)
|
|
else
|
|
SERVICE_JSON+=$',
|
|
"nodes": ['
|
|
for ((i=0; i<NODE_COUNT; i++)); do
|
|
NODE_BLOCK=$(cat <<EOF
|
|
{
|
|
"node_id": ${NODE_IDS[$i]}
|
|
EOF
|
|
)
|
|
NODE_BLOCK+=$'\n }'
|
|
if [[ "$i" -gt 0 ]]; then
|
|
SERVICE_JSON+=','
|
|
fi
|
|
SERVICE_JSON+=$'\n'"$NODE_BLOCK"
|
|
done
|
|
SERVICE_JSON+=$'\n ]'
|
|
fi
|
|
SERVICE_JSON+=$'\n }'
|
|
|
|
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": [
|
|
${DNS_SERVER_JSON}
|
|
]
|
|
},
|
|
"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
|
|
|
|
echo -e "${YELLOW}Creating systemd service...${NC}"
|
|
cat > "$SERVICE_FILE" <<EOF
|
|
[Unit]
|
|
Description=singbox 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
|
|
|
|
systemctl daemon-reload
|
|
systemctl enable "$SERVICE_NAME"
|
|
systemctl restart "$SERVICE_NAME"
|
|
|
|
prompt_uninstall_v2bx
|
|
|
|
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}"
|