Files
jiedian/scripts/bootstrap.sh
T
2026-07-11 13:49:20 +08:00

327 lines
9.5 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
# 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