feat: add bootstrap one-click deploy and update deployment docs

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-07-11 13:49:20 +08:00
parent 46728e12f7
commit 1f36a76ce0
11 changed files with 525 additions and 223 deletions
+326
View File
@@ -0,0 +1,326 @@
#!/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