1f36a76ce0
Co-authored-by: Cursor <cursoragent@cursor.com>
327 lines
9.5 KiB
Bash
327 lines
9.5 KiB
Bash
#!/usr/bin/env bash
|
||
# jiedian 一键部署入口:自动 clone、交互配置、调用 install.sh
|
||
# 用法:
|
||
# curl -fsSL https://git.bz121.com/dekun/jiedian/raw/main/scripts/bootstrap.sh | bash
|
||
# bash scripts/bootstrap.sh
|
||
# 非交互:
|
||
# bash scripts/bootstrap.sh --domain example.com --email you@example.com --yes
|
||
set -euo pipefail
|
||
|
||
INSTALL_DIR="/opt/jiedian"
|
||
REPO_URL="https://git.bz121.com/dekun/jiedian.git"
|
||
REPO_BRANCH="main"
|
||
CREDS_FILE="/root/jiedian-credentials.txt"
|
||
|
||
RED='\033[0;31m'
|
||
GREEN='\033[0;32m'
|
||
CYAN='\033[0;36m'
|
||
YELLOW='\033[1;33m'
|
||
BOLD='\033[1m'
|
||
NC='\033[0m'
|
||
|
||
log() { echo -e "${GREEN}[+]${NC} $*"; }
|
||
warn() { echo -e "${YELLOW}[!]${NC} $*"; }
|
||
err() { echo -e "${RED}[!]${NC} $*" >&2; exit 1; }
|
||
|
||
usage() {
|
||
cat <<'EOF'
|
||
用法:
|
||
curl -fsSL https://git.bz121.com/dekun/jiedian/raw/main/scripts/bootstrap.sh | bash
|
||
|
||
bash scripts/bootstrap.sh [选项]
|
||
|
||
选项:
|
||
--domain DOMAIN 域名(Hy2 + 面板 HTTPS)
|
||
--email EMAIL Let's Encrypt 证书邮箱
|
||
--username USER 面板登录用户名(默认 admin)
|
||
--password PASS 面板登录密码(留空则随机生成)
|
||
--yes, -y 跳过确认
|
||
-h, --help 显示帮助
|
||
EOF
|
||
}
|
||
|
||
ensure_repo() {
|
||
if [[ -d "$INSTALL_DIR/.git" ]] && [[ -f "$INSTALL_DIR/scripts/install.sh" ]]; then
|
||
return 0
|
||
fi
|
||
|
||
log "安装 git ..."
|
||
export DEBIAN_FRONTEND=noninteractive
|
||
apt-get update -qq
|
||
apt-get install -y -qq git ca-certificates curl
|
||
|
||
if [[ -d "$INSTALL_DIR" ]] && [[ ! -d "$INSTALL_DIR/.git" ]]; then
|
||
warn "目录 $INSTALL_DIR 已存在但不是 git 仓库,将备份为 ${INSTALL_DIR}.bak"
|
||
mv "$INSTALL_DIR" "${INSTALL_DIR}.bak.$(date +%s)"
|
||
fi
|
||
|
||
log "克隆仓库到 $INSTALL_DIR ..."
|
||
git clone --depth 1 -b "$REPO_BRANCH" "$REPO_URL" "$INSTALL_DIR"
|
||
}
|
||
|
||
reexec_from_repo() {
|
||
[[ "${JIEDIAN_BOOTSTRAP_FROM_REPO:-}" == "1" ]] && return 0
|
||
local repo_bootstrap="$INSTALL_DIR/scripts/bootstrap.sh"
|
||
[[ -f "$repo_bootstrap" ]] || return 0
|
||
|
||
local src="${BASH_SOURCE[0]:-}"
|
||
case "$src" in
|
||
"$repo_bootstrap"|*/jiedian/scripts/bootstrap.sh) return 0 ;;
|
||
esac
|
||
|
||
export JIEDIAN_BOOTSTRAP_FROM_REPO=1
|
||
log "使用仓库内 bootstrap 继续 ..."
|
||
exec bash "$repo_bootstrap" "$@"
|
||
}
|
||
|
||
check_root() {
|
||
[[ $EUID -eq 0 ]] || err "请使用 root 运行(或 sudo bash bootstrap.sh)"
|
||
}
|
||
|
||
check_os() {
|
||
[[ -f /etc/os-release ]] || err "无法识别操作系统"
|
||
# shellcheck disable=SC1091
|
||
source /etc/os-release
|
||
[[ "${ID:-}" == "ubuntu" ]] || err "仅支持 Ubuntu,当前: ${ID:-unknown}"
|
||
case "${VERSION_ID:-}" in
|
||
22.04|24.04) ;;
|
||
*) err "仅支持 Ubuntu 22.04 / 24.04,当前: ${VERSION_ID:-unknown}" ;;
|
||
esac
|
||
}
|
||
|
||
detect_public_ip() {
|
||
curl -4 -fsSL --connect-timeout 5 ifconfig.me 2>/dev/null \
|
||
|| curl -4 -fsSL --connect-timeout 5 ip.sb 2>/dev/null \
|
||
|| curl -4 -fsSL --connect-timeout 5 icanhazip.com 2>/dev/null \
|
||
|| err "无法检测公网 IP,请检查网络"
|
||
}
|
||
|
||
resolve_domain() {
|
||
local domain="$1"
|
||
if command -v dig >/dev/null 2>&1; then
|
||
dig +short A "$domain" @8.8.8.8 2>/dev/null | grep -E '^[0-9.]+$' | head -1
|
||
return 0
|
||
fi
|
||
getent ahostsv4 "$domain" 2>/dev/null | awk '{print $1; exit}'
|
||
}
|
||
|
||
valid_domain() {
|
||
[[ "$1" =~ ^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$ ]]
|
||
}
|
||
|
||
valid_email() {
|
||
[[ "$1" == *"@"* ]] && [[ "$1" == *"."* ]]
|
||
}
|
||
|
||
prompt_if_empty() {
|
||
local var_name="$1"
|
||
local prompt="$2"
|
||
local default="${3:-}"
|
||
local secret="${4:-0}"
|
||
local value=""
|
||
|
||
if [[ -n "${!var_name:-}" ]]; then
|
||
return 0
|
||
fi
|
||
|
||
if [[ -n "$default" ]]; then
|
||
prompt="$prompt [$default]"
|
||
fi
|
||
|
||
if [[ "$secret" == "1" ]]; then
|
||
read -r -s -p "$prompt: " value
|
||
echo ""
|
||
else
|
||
read -r -p "$prompt: " value
|
||
fi
|
||
|
||
if [[ -z "$value" && -n "$default" ]]; then
|
||
value="$default"
|
||
fi
|
||
|
||
printf -v "$var_name" '%s' "$value"
|
||
}
|
||
|
||
generate_password() {
|
||
openssl rand -base64 32 | tr -d '/+=' | head -c 20
|
||
}
|
||
|
||
write_env_file() {
|
||
local env_file="$INSTALL_DIR/.env"
|
||
cat > "$env_file" <<EOF
|
||
# 由 bootstrap.sh 自动生成
|
||
VPS_IP=${VPS_IP}
|
||
DOMAIN=${DOMAIN}
|
||
ACME_EMAIL=${ACME_EMAIL}
|
||
PANEL_USERNAME=${PANEL_USERNAME}
|
||
PANEL_PASSWORD=${PANEL_PASSWORD}
|
||
EOF
|
||
chmod 600 "$env_file"
|
||
}
|
||
|
||
print_banner() {
|
||
echo ""
|
||
echo -e "${CYAN}${BOLD}╔══════════════════════════════════════╗${NC}"
|
||
echo -e "${CYAN}${BOLD}║ jiedian 一键部署 ║${NC}"
|
||
echo -e "${CYAN}${BOLD}║ Hysteria2 + Web 管理面板 ║${NC}"
|
||
echo -e "${CYAN}${BOLD}╚══════════════════════════════════════╝${NC}"
|
||
echo ""
|
||
echo "部署前请确认:"
|
||
echo " □ 域名 A 记录已指向本机公网 IP"
|
||
echo " □ 安全组已放行 22/TCP 80/TCP 443/TCP 8443-8499/UDP"
|
||
echo ""
|
||
}
|
||
|
||
confirm_or_abort() {
|
||
local answer=""
|
||
echo ""
|
||
echo -e "${BOLD}即将写入配置:${NC}"
|
||
echo " 公网 IP: $VPS_IP"
|
||
echo " 域名: $DOMAIN"
|
||
echo " 证书邮箱: $ACME_EMAIL"
|
||
echo " 用户名: $PANEL_USERNAME"
|
||
echo " 密码: ${PANEL_PASSWORD:0:2}****** (${#PANEL_PASSWORD} 位)"
|
||
echo ""
|
||
read -r -p "确认开始部署?(Y/n): " answer
|
||
case "${answer:-Y}" in
|
||
Y|y|yes|YES) ;;
|
||
*) err "已取消" ;;
|
||
esac
|
||
}
|
||
|
||
check_existing_install() {
|
||
local env_file="$INSTALL_DIR/.env"
|
||
[[ -f "$env_file" ]] || return 0
|
||
|
||
warn "检测到已有部署($env_file)"
|
||
local answer=""
|
||
read -r -p "是否重新部署?将覆盖 .env 并重新安装 (y/N): " answer
|
||
case "${answer:-N}" in
|
||
y|Y|yes|YES) ;;
|
||
*) err "已取消。若要更新代码请: cd $INSTALL_DIR && git pull" ;;
|
||
esac
|
||
}
|
||
|
||
save_credentials() {
|
||
# shellcheck disable=SC1091
|
||
source "$INSTALL_DIR/.env"
|
||
local panel_path="${PANEL_PATH:-}"
|
||
if [[ -z "$panel_path" ]]; then
|
||
panel_path="$(grep -E '^PANEL_PATH=' "$INSTALL_DIR/.env" | cut -d= -f2- || true)"
|
||
fi
|
||
[[ -n "$panel_path" ]] || panel_path="jiedian-unknown"
|
||
|
||
cat > "$CREDS_FILE" <<EOF
|
||
jiedian 部署凭证
|
||
生成时间: $(date -Iseconds)
|
||
|
||
面板地址: https://${DOMAIN}/${panel_path}/
|
||
用户名: ${PANEL_USERNAME}
|
||
密码: ${PANEL_PASSWORD}
|
||
面板路径: ${panel_path}
|
||
VPS IP: ${VPS_IP}
|
||
EOF
|
||
chmod 600 "$CREDS_FILE"
|
||
}
|
||
|
||
print_success() {
|
||
# shellcheck disable=SC1091
|
||
source "$INSTALL_DIR/.env"
|
||
local panel_path="${PANEL_PATH:-jiedian-unknown}"
|
||
|
||
echo ""
|
||
echo -e "${GREEN}${BOLD}╔══════════════════════════════════════════════════════╗${NC}"
|
||
echo -e "${GREEN}${BOLD}║ 部署完成 ║${NC}"
|
||
echo -e "${GREEN}${BOLD}╠══════════════════════════════════════════════════════╣${NC}"
|
||
echo -e "${GREEN}${BOLD}║${NC} 面板地址: ${CYAN}https://${DOMAIN}/${panel_path}/${NC}"
|
||
echo -e "${GREEN}${BOLD}║${NC} 用户名: ${PANEL_USERNAME}"
|
||
echo -e "${GREEN}${BOLD}║${NC} 密码: ${PANEL_PASSWORD}"
|
||
echo -e "${GREEN}${BOLD}╠══════════════════════════════════════════════════════╣${NC}"
|
||
echo -e "${GREEN}${BOLD}║${NC} 凭证已保存: ${CREDS_FILE}"
|
||
echo -e "${GREEN}${BOLD}╚══════════════════════════════════════════════════════╝${NC}"
|
||
echo ""
|
||
echo "下一步:浏览器打开面板 → 登录 → 添加节点 → 复制 Hy2 链接到客户端"
|
||
echo ""
|
||
}
|
||
|
||
# ── 参数解析 ──
|
||
DOMAIN=""
|
||
ACME_EMAIL=""
|
||
PANEL_USERNAME=""
|
||
PANEL_PASSWORD=""
|
||
ASSUME_YES=0
|
||
|
||
while [[ $# -gt 0 ]]; do
|
||
case "$1" in
|
||
--domain) DOMAIN="$2"; shift 2 ;;
|
||
--email) ACME_EMAIL="$2"; shift 2 ;;
|
||
--username) PANEL_USERNAME="$2"; shift 2 ;;
|
||
--password) PANEL_PASSWORD="$2"; shift 2 ;;
|
||
--yes|-y) ASSUME_YES=1; shift ;;
|
||
-h|--help) usage; exit 0 ;;
|
||
*) err "未知参数: $1(使用 --help 查看)" ;;
|
||
esac
|
||
done
|
||
|
||
check_root
|
||
check_os
|
||
|
||
# curl | bash 时先 clone,再用仓库内脚本继续
|
||
ensure_repo
|
||
reexec_from_repo "$@"
|
||
|
||
print_banner
|
||
|
||
ROOT_DIR="$INSTALL_DIR"
|
||
ENV_FILE="$ROOT_DIR/.env"
|
||
|
||
check_existing_install
|
||
|
||
VPS_IP="$(detect_public_ip)"
|
||
log "检测到公网 IP: $VPS_IP"
|
||
|
||
prompt_if_empty DOMAIN "域名 (Hy2 + 面板 HTTPS)"
|
||
prompt_if_empty ACME_EMAIL "证书邮箱 (Let's Encrypt)"
|
||
prompt_if_empty PANEL_USERNAME "面板用户名" "admin"
|
||
|
||
valid_domain "$DOMAIN" || err "域名格式无效: $DOMAIN"
|
||
valid_email "$ACME_EMAIL" || err "邮箱格式无效: $ACME_EMAIL"
|
||
[[ -n "$PANEL_USERNAME" ]] || err "用户名不能为空"
|
||
|
||
if [[ -z "$PANEL_PASSWORD" ]]; then
|
||
read -r -s -p "面板密码 (留空自动生成): " PANEL_PASSWORD
|
||
echo ""
|
||
fi
|
||
if [[ -z "$PANEL_PASSWORD" ]]; then
|
||
PANEL_PASSWORD="$(generate_password)"
|
||
log "已自动生成面板密码"
|
||
fi
|
||
|
||
RESOLVED_IP="$(resolve_domain "$DOMAIN" || true)"
|
||
if [[ -z "$RESOLVED_IP" ]]; then
|
||
err "域名 $DOMAIN 无法解析,请先将 A 记录指向 $VPS_IP"
|
||
fi
|
||
if [[ "$RESOLVED_IP" != "$VPS_IP" ]]; then
|
||
warn "域名 $DOMAIN 解析为 $RESOLVED_IP,本机公网 IP 为 $VPS_IP"
|
||
if [[ "$ASSUME_YES" -eq 0 ]]; then
|
||
read -r -p "DNS 可能未生效,继续?(y/N): " dns_ok
|
||
case "${dns_ok:-N}" in
|
||
y|Y|yes|YES) ;;
|
||
*) err "请先修正 DNS 后重试" ;;
|
||
esac
|
||
fi
|
||
fi
|
||
|
||
if [[ "$ASSUME_YES" -eq 0 ]]; then
|
||
confirm_or_abort
|
||
fi
|
||
|
||
write_env_file
|
||
log "已写入 $ENV_FILE"
|
||
|
||
log "开始安装 ..."
|
||
bash "$ROOT_DIR/scripts/install.sh"
|
||
|
||
save_credentials
|
||
print_success
|