78b85c0d83
Add Nginx SSL panel config, enable-panel-https.sh, secure Flask cookies, and update docs for https login. Co-authored-by: Cursor <cursoragent@cursor.com>
244 lines
7.4 KiB
Bash
244 lines
7.4 KiB
Bash
#!/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}"
|
||
: "${PANEL_USERNAME:=admin}"
|
||
|
||
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
|
||
|
||
: "${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'
|
||
ufw allow 443/tcp comment 'Panel-HTTPS'
|
||
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" \
|
||
"$ROOT_DIR/server/nginx/acme-bootstrap.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
|
||
|
||
log "部署 Nginx HTTPS 管理面板 (443) ..."
|
||
bash "$ROOT_DIR/scripts/enable-panel-https.sh"
|
||
|
||
rm -f /etc/nginx/sites-enabled/panel /etc/nginx/sites-available/panel
|
||
|
||
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 "创建 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 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 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 sing-box jiedian-panel
|
||
|
||
log "部署完成!"
|
||
echo ""
|
||
echo "=========================================="
|
||
echo " 管理面板: https://${DOMAIN}${PANEL_LOCATION}"
|
||
echo " (HTTP 会自动跳转到 HTTPS)"
|
||
echo " 面板路径: ${PANEL_PATH} (见 .env 中 PANEL_PATH)"
|
||
echo " 用户名: ${PANEL_USERNAME}"
|
||
echo " 密码: ${PANEL_PASSWORD}"
|
||
echo "=========================================="
|
||
echo ""
|
||
echo "节点链接请在面板中添加/复制。"
|
||
echo ""
|
||
log "sing-box: systemctl status sing-box"
|
||
log "面板: systemctl status jiedian-panel"
|
||
log "卸载重装: bash scripts/uninstall.sh && bash scripts/install.sh"
|