Files
jiedian/scripts/install.sh
T
dekun c9895133cb fix: run VLESS Reality on Xray instead of sing-box for v2rayN
sing-box Hy2 stays on 8443+; port 443 VLESS uses Xray which pairs reliably with v2rayN/Xray-core clients.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-16 11:56:22 +08:00

261 lines
8.0 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# VPS 一键部署:sing-box + Web 管理面板
# 用法:sudo bash scripts/install.sh
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="$(dirname "$SCRIPT_DIR")"
ENV_FILE="${ROOT_DIR}/.env"
RED='\033[0;31m'
GREEN='\033[0;32m'
NC='\033[0m'
log() { echo -e "${GREEN}[+]${NC} $*"; }
err() { echo -e "${RED}[!]${NC} $*" >&2; exit 1; }
wait_for_apt() {
local i=0
while fuser /var/lib/dpkg/lock-frontend >/dev/null 2>&1; do
if (( i == 0 )); then
log "等待 apt 锁释放(系统自动更新中)..."
fi
(( i++ )) || true
if (( i > 120 )); then
err "apt 锁等待超时,请稍后重试: bash scripts/install.sh"
fi
sleep 5
done
}
[[ $EUID -eq 0 ]] || err "请使用 root 运行: sudo bash scripts/install.sh"
[[ -f "$ENV_FILE" ]] || err "缺少 .env 文件,请先: cp .env.example .env 并填写"
# shellcheck disable=SC1090
source "$ENV_FILE"
: "${VPS_IP:?请在 .env 中设置 VPS_IP}"
: "${DOMAIN:?请在 .env 中设置 DOMAIN}"
: "${ACME_EMAIL:?请在 .env 中设置 ACME_EMAIL}"
: "${REALITY_SERVER_NAME:=www.microsoft.com}"
: "${PANEL_USERNAME:=admin}"
if [[ -z "${REALITY_PRIVATE_KEY:-}" ]]; then
log "未检测到 Reality 密钥,运行 generate-keys.sh ..."
bash "$SCRIPT_DIR/generate-keys.sh"
source "$ENV_FILE"
fi
if [[ -z "${PANEL_PASSWORD:-}" ]]; then
PANEL_PASSWORD="$(sing-box generate rand --base64 32 | tr -d '/+=' | head -c 20)"
if grep -q "^PANEL_PASSWORD=" "$ENV_FILE" 2>/dev/null; then
sed -i "s|^PANEL_PASSWORD=.*|PANEL_PASSWORD=${PANEL_PASSWORD}|" "$ENV_FILE"
else
echo "PANEL_PASSWORD=${PANEL_PASSWORD}" >> "$ENV_FILE"
fi
source "$ENV_FILE"
fi
: "${REALITY_PRIVATE_KEY:?}"
: "${REALITY_PUBLIC_KEY:?}"
: "${REALITY_SHORT_ID:?}"
: "${PANEL_PASSWORD:?}"
if [[ -z "${CLASH_API_SECRET:-}" ]]; then
CLASH_API_SECRET="$(openssl rand -hex 16)"
if grep -q "^CLASH_API_SECRET=" "$ENV_FILE" 2>/dev/null; then
sed -i "s|^CLASH_API_SECRET=.*|CLASH_API_SECRET=${CLASH_API_SECRET}|" "$ENV_FILE"
else
echo "CLASH_API_SECRET=${CLASH_API_SECRET}" >> "$ENV_FILE"
fi
source "$ENV_FILE"
fi
normalize_panel_path() {
local p="${1:-}"
p="${p#/}"
p="${p%/}"
echo "$p"
}
PANEL_PATH="$(normalize_panel_path "${PANEL_PATH:-}")"
if [[ -z "$PANEL_PATH" ]]; then
PANEL_PATH="jiedian-$(openssl rand -hex 4)"
if grep -q "^PANEL_PATH=" "$ENV_FILE" 2>/dev/null; then
sed -i "s|^PANEL_PATH=.*|PANEL_PATH=${PANEL_PATH}|" "$ENV_FILE"
else
echo "PANEL_PATH=${PANEL_PATH}" >> "$ENV_FILE"
fi
fi
PANEL_LOCATION="/${PANEL_PATH}/"
PANEL_PREFIX="/${PANEL_PATH}"
PANEL_ALLOW_BLOCK=""
if [[ -n "${PANEL_ALLOW_IP:-}" ]]; then
PANEL_ALLOW_BLOCK=" allow ${PANEL_ALLOW_IP};
deny all;"
fi
export JIEDIAN_ROOT="$ROOT_DIR"
ARCH="$(uname -m)"
case "$ARCH" in
x86_64) SB_ARCH="amd64" ;;
aarch64) SB_ARCH="arm64" ;;
*) err "不支持的架构: $ARCH" ;;
esac
SB_VERSION="1.11.0"
SB_URL="https://github.com/SagerNet/sing-box/releases/download/v${SB_VERSION}/sing-box-${SB_VERSION}-linux-${SB_ARCH}.tar.gz"
wait_for_apt
log "更新系统包 ..."
export DEBIAN_FRONTEND=noninteractive
apt-get update -qq
apt-get install -y -qq curl wget nginx ufw ca-certificates python3 python3-venv python3-pip
log "安装 sing-box ${SB_VERSION} ..."
TMP="$(mktemp -d)"
curl -fsSL "$SB_URL" | tar -xz -C "$TMP" --strip-components=1
install -m 755 "$TMP/sing-box" /usr/local/bin/sing-box
rm -rf "$TMP"
log "配置防火墙 ..."
ufw --force reset
ufw default deny incoming
ufw default allow outgoing
ufw allow 22/tcp comment 'SSH'
ufw allow 80/tcp comment 'HTTP-ACME-Panel'
ufw allow 443/tcp comment 'Reality'
ufw allow 8443:8499/udp comment 'Hysteria2-multi-node'
ufw --force enable
log "部署 Nginx fallback 站点 ..."
mkdir -p /var/www/fallback
cp "$ROOT_DIR/server/nginx/index.html" /var/www/fallback/
cp "$ROOT_DIR/server/nginx/fallback.conf" /etc/nginx/sites-available/fallback
ln -sf /etc/nginx/sites-available/fallback /etc/nginx/sites-enabled/fallback
rm -f /etc/nginx/sites-enabled/default
log "部署 Nginx ACME + 管理面板反向代理 (80) ..."
mkdir -p /var/www/acme
sed -e "s|__DOMAIN__|${DOMAIN}|g" \
-e "s|__PANEL_LOCATION__|${PANEL_LOCATION}|g" \
-e "s|__PANEL_PREFIX__|${PANEL_PREFIX}|g" \
-e "s|__PANEL_ALLOW__|${PANEL_ALLOW_BLOCK}|g" \
"$ROOT_DIR/server/nginx/acme.conf.template" \
> /etc/nginx/sites-available/acme
ln -sf /etc/nginx/sites-available/acme /etc/nginx/sites-enabled/acme
nginx -t && systemctl enable nginx && systemctl restart nginx
log "申请 TLS 证书 (Let's Encrypt) ..."
mkdir -p /etc/sing-box/certs
if [[ ! -f /root/.acme.sh/acme.sh ]]; then
curl -fsSL https://get.acme.sh | sh -s email="$ACME_EMAIL"
fi
# shellcheck disable=SC1091
source /root/.acme.sh/acme.sh.env || true
CURRENT_IP="$(curl -4 -fsSL ifconfig.me 2>/dev/null || curl -4 -fsSL ip.sb)"
if [[ "$CURRENT_IP" != "$VPS_IP" ]]; then
err "域名 $DOMAIN 需先解析到 VPS IP ($VPS_IP),当前 VPS 出口 IP 为 $CURRENT_IP"
fi
/root/.acme.sh/acme.sh --set-default-ca --server letsencrypt
if [[ ! -f "/root/.acme.sh/${DOMAIN}_ecc/fullchain.cer" ]]; then
/root/.acme.sh/acme.sh --issue -d "$DOMAIN" -w /var/www/acme --force
fi
log "安装 TLS 证书到 sing-box ..."
/root/.acme.sh/acme.sh --install-cert -d "$DOMAIN" \
--key-file /etc/sing-box/certs/privkey.pem \
--fullchain-file /etc/sing-box/certs/fullchain.pem
rm -f /etc/nginx/sites-enabled/panel /etc/nginx/sites-available/panel
nginx -t && systemctl reload nginx
log "安装 Python 面板依赖 ..."
python3 -m venv "$ROOT_DIR/panel/venv"
"$ROOT_DIR/panel/venv/bin/pip" install -q --upgrade pip
"$ROOT_DIR/panel/venv/bin/pip" install -q -r "$ROOT_DIR/panel/requirements.txt"
"$ROOT_DIR/panel/venv/bin/python" -c "import flask" \
|| err "面板依赖安装失败,请检查网络后重试"
log "初始化节点数据库 ..."
"$ROOT_DIR/panel/venv/bin/python" "$ROOT_DIR/panel/init_db.py"
log "生成 sing-box 服务端配置 (Hysteria2) ..."
python3 "$ROOT_DIR/scripts/render-server.py"
log "安装 Xray (VLESS Reality 443) ..."
bash -c "$(curl -fsSL https://github.com/XTLS/Xray-install/raw/main/install-release.sh)" @ install
log "生成 Xray 服务端配置 ..."
python3 "$ROOT_DIR/scripts/render-xray.py"
log "创建 sing-box systemd 服务 ..."
cat > /etc/systemd/system/sing-box.service <<'UNIT'
[Unit]
Description=sing-box service
After=network-online.target nginx.service
Wants=network-online.target
[Service]
Type=simple
ExecStart=/usr/local/bin/sing-box run -c /etc/sing-box/config.json
Restart=on-failure
RestartSec=5
LimitNOFILE=1048576
[Install]
WantedBy=multi-user.target
UNIT
log "创建管理面板 systemd 服务 ..."
cat > /etc/systemd/system/jiedian-panel.service <<UNIT
[Unit]
Description=jiedian admin panel
After=network.target xray.service sing-box.service
[Service]
Type=simple
WorkingDirectory=${ROOT_DIR}/panel
Environment=JIEDIAN_ROOT=${ROOT_DIR}
Environment=PANEL_PATH=${PANEL_PATH}
Environment=PANEL_DOMAIN=${DOMAIN}
ExecStart=${ROOT_DIR}/panel/venv/bin/python app.py
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
UNIT
systemctl daemon-reload
systemctl enable xray sing-box jiedian-panel
log "注册证书续期 reload 命令 ..."
/root/.acme.sh/acme.sh --install-cert -d "$DOMAIN" \
--key-file /etc/sing-box/certs/privkey.pem \
--fullchain-file /etc/sing-box/certs/fullchain.pem \
--reloadcmd "systemctl restart sing-box && systemctl reload nginx" \
|| log "acme reloadcmd 注册失败,可忽略"
systemctl restart xray sing-box jiedian-panel
log "部署完成!"
echo ""
echo "=========================================="
echo " 管理面板: http://${DOMAIN}${PANEL_LOCATION}"
echo " 面板路径: ${PANEL_PATH} (见 .env 中 PANEL_PATH"
echo " 用户名: ${PANEL_USERNAME}"
echo " 密码: ${PANEL_PASSWORD}"
echo "=========================================="
echo ""
echo "节点链接请在面板中添加/复制。"
echo ""
log "sing-box: systemctl status sing-box"
log "Xray: systemctl status xray"
log "面板: systemctl status jiedian-panel"
log "卸载重装: bash scripts/uninstall.sh && bash scripts/install.sh"