feat: add bootstrap one-click deploy and update deployment docs
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -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
|
||||
Reference in New Issue
Block a user