easyai/start.sh
wangbolhr 5c856749c1
Some checks are pending
Test start.ps1 (Windows) / test-windows (push) Waiting to run
fix(deploy): 浅克隆说明、README 精简与 Docker 自动安装可观测性
- README: Windows 一条 PowerShell 命令,保留脚本权限说明;合并重复段落
- start.sh/ps1: git clone --depth 1 示例
- start.ps1: 选 [2] 时输出 winget/choco 日志与退出码,失败原因可辨;Read-Host Trim;winget 后若 docker 可用则视为成功

Made-with: Cursor
2026-04-10 17:09:18 +08:00

437 lines
16 KiB
Bash
Executable File
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.

#!/bin/bash
# EasyAI 一键部署脚本
# 支持交互式问答配置,兼容 IP 与域名两种访问方式
# 一行命令: git clone --depth 1 https://git.51easyai.com/wangbo/easyai.git && cd easyai && chmod +x ./start.sh && ./start.sh
set -e
# 仅配置模式验证用DEPLOY_DRY_RUN=1 只生成配置文件,不执行 Docker 安装和启动
DEPLOY_DRY_RUN="${DEPLOY_DRY_RUN:-0}"
# ==================== 项目初始化 ====================
init_project_dir() {
local script_source
script_source="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)"
if [ -f "${script_source}/docker-compose.yml" ]; then
echo "📁 项目目录: ${script_source}"
cd "$script_source"
return 0
fi
echo "❌ 未找到 docker-compose.yml请在 easyai 项目目录下运行 start.sh"
echo " 启动命令: git clone --depth 1 https://git.51easyai.com/wangbo/easyai.git && cd easyai && chmod +x ./start.sh && ./start.sh"
exit 1
}
# ==================== 配置变量(支持环境变量非交互模式) ====================
DEPLOY_MODE="" # ip | domain
DEPLOY_IP=""
DEPLOY_DOMAIN=""
DEPLOY_HTTPS=false
sanitize_domain() {
local domain="$1"
domain="${domain#http://}"
domain="${domain#https://}"
domain="${domain%%/*}"
domain="${domain%;}"
echo "$domain"
}
validate_domain() {
local domain="$1"
# 基础域名校验:仅允许字母、数字、连字符和点;必须包含至少一个点;不能以点或连字符开头结尾
if [[ ! "$domain" =~ ^[A-Za-z0-9.-]+$ ]]; then
return 1
fi
if [[ "$domain" != *.* ]]; then
return 1
fi
if [[ "$domain" =~ ^[.-] || "$domain" =~ [.-]$ ]]; then
return 1
fi
if [[ "$domain" == *".."* ]]; then
return 1
fi
return 0
}
prompt_or_env() {
local var_name=$1
local prompt_text=$2
local env_name=$3
local default=$4
if [ -n "${!env_name}" ]; then
eval "$var_name=\"${!env_name}\""
return
fi
if [ -n "$default" ]; then
read -r -p "${prompt_text} [$default]: " input
eval "$var_name=\"${input:-$default}\""
else
read -r -p "${prompt_text}: " input
eval "$var_name=\"$input\""
fi
}
run_deploy_questions() {
echo ""
echo "================================"
echo " EasyAI 部署配置(问答模式)"
echo "================================"
echo ""
# 非交互模式环境变量已完整设置则直接使用CI/自动化部署)
if [ -n "$DEPLOY_ACCESS" ]; then
if [ "$DEPLOY_ACCESS" = "ip" ] && [ -n "$DEPLOY_IP" ]; then
DEPLOY_MODE="ip"
echo "使用环境变量: IP 模式, IP=$DEPLOY_IP"
return
fi
if [ "$DEPLOY_ACCESS" = "domain" ] && [ -n "$DEPLOY_DOMAIN" ]; then
DEPLOY_DOMAIN="$(sanitize_domain "$DEPLOY_DOMAIN")"
if ! validate_domain "$DEPLOY_DOMAIN"; then
echo "❌ 环境变量 DEPLOY_DOMAIN 格式不合法: ${DEPLOY_DOMAIN}"
exit 1
fi
DEPLOY_MODE="domain"
DEPLOY_HTTPS="${DEPLOY_HTTPS_INPUT:-false}"
echo "使用环境变量: 域名模式, 域名=$DEPLOY_DOMAIN"
return
fi
fi
# 1. IP 或域名访问
if [ -z "$DEPLOY_ACCESS" ]; then
echo "1. 通过 IP 地址还是域名访问?"
echo " [1] IP 地址(需开放 3001、3002、3003 端口)"
echo " [2] 域名"
read -r -p "请选择 [1/2]: " choice
case "$choice" in
1) DEPLOY_MODE="ip" ;;
2) DEPLOY_MODE="domain" ;;
*) echo "❌ 无效选择"; exit 1 ;;
esac
else
DEPLOY_MODE="$DEPLOY_ACCESS"
fi
if [ "$DEPLOY_MODE" = "ip" ]; then
# 2. 输入服务器 IP
prompt_or_env DEPLOY_IP "2. 请输入服务器 IP 地址" "DEPLOY_IP" ""
if [ -z "$DEPLOY_IP" ]; then
echo "❌ IP 不能为空"
exit 1
fi
echo " 请确保防火墙已放行 3001、3002、3003 端口"
else
# 3. 输入域名
prompt_or_env DEPLOY_DOMAIN "3. 请输入域名(不含 https:// 前缀,如 51easyai.com" "DEPLOY_DOMAIN" ""
DEPLOY_DOMAIN="$(sanitize_domain "$DEPLOY_DOMAIN")"
if [ -z "$DEPLOY_DOMAIN" ]; then
echo "❌ 域名不能为空"
exit 1
fi
if ! validate_domain "$DEPLOY_DOMAIN"; then
echo "❌ 域名格式不合法: ${DEPLOY_DOMAIN}"
echo " 请输入类似 51easyai.com 的域名,不要包含协议、路径或分号"
exit 1
fi
# 3.1 是否启用 HTTPS
if [ -n "$DEPLOY_HTTPS_INPUT" ]; then
DEPLOY_HTTPS=$DEPLOY_HTTPS_INPUT
else
read -r -p "3.1 是否签名 HTTPS 证书?原来已经有 HTTPS 证书这里选择 N第一次部署或者原来没有证书选 Y [y/N]: " https_choice
DEPLOY_HTTPS=false
if [[ "$https_choice" =~ ^[yY] ]]; then
DEPLOY_HTTPS=true
fi
fi
if [ "$DEPLOY_HTTPS" = true ]; then
echo " 启用 HTTPS 需确保防火墙已放行 80、443 端口"
fi
fi
}
# ==================== 生成配置文件 ====================
setup_env_files() {
echo ""
echo "📝 配置环境文件..."
# 6. 复制 .env.tools、.env.ASG、.env.AMS无 example 后缀的从 .sample 生成)
if [ ! -f .env.tools ]; then
cp .env.tools.sample .env.tools
echo " ✓ .env.tools"
fi
if [ ! -f .env.ASG ]; then
cp .env.ASG.sample .env.ASG
echo " ✓ .env.ASG"
fi
if [ ! -f .env.AMS ]; then
cp .env.AMS.sample .env.AMS
echo " ✓ .env.AMS"
fi
# 4/5. 配置 .env
if [ ! -f .env ]; then
cp .env.sample .env
fi
if [ "$DEPLOY_MODE" = "ip" ]; then
# IP 模式
sed -i.bak "s|^NUXT_PUBLIC_BASE_APIURL=.*|NUXT_PUBLIC_BASE_APIURL=http://${DEPLOY_IP}:3001|" .env
sed -i.bak "s|^NUXT_PUBLIC_BASE_SOCKETURL=.*|NUXT_PUBLIC_BASE_SOCKETURL=ws://${DEPLOY_IP}:3002|" .env
sed -i.bak "s|^NUXT_PUBLIC_SG_APIURL=.*|NUXT_PUBLIC_SG_APIURL=http://${DEPLOY_IP}:3003|" .env
echo " ✓ .env 已配置为 IP 模式 (${DEPLOY_IP})"
else
# 域名模式
sed -i.bak "s|^NUXT_PUBLIC_BASE_APIURL=.*|NUXT_PUBLIC_BASE_APIURL=/api|" .env
sed -i.bak "s|^NUXT_PUBLIC_BASE_SOCKETURL=.*|NUXT_PUBLIC_BASE_SOCKETURL=wss://${DEPLOY_DOMAIN}/socket.io|" .env
sed -i.bak "s|^NUXT_PUBLIC_SG_APIURL=.*|NUXT_PUBLIC_SG_APIURL=/asg-api|" .env
echo " ✓ .env 已配置为域名模式 (${DEPLOY_DOMAIN})"
# 7. Nginx 配置(域名模式)
PROXY_CONF="${DEPLOY_DOMAIN}.conf"
if [ ! -f "$PROXY_CONF" ]; then
local proxy_template="easyai-proxy.conf.sample"
if [ ! -f "$proxy_template" ]; then
echo "❌ 未找到 Nginx 配置模板: easyai-proxy.conf.sample"
echo " 请确认仓库文件完整后重试"
exit 1
fi
sed "s/51easyai.com/${DEPLOY_DOMAIN}/g" "$proxy_template" > "$PROXY_CONF"
echo " ✓ Nginx 配置已生成: $PROXY_CONF"
fi
fi
rm -f .env.bak
}
# ==================== Docker 安装(复用原 start.sh 逻辑) ====================
install_docker() {
echo ""
echo "================================"
echo " Docker 安装与检查"
echo "================================"
local host_os
host_os="$(uname -s 2>/dev/null || echo Unknown)"
if [[ "$host_os" == "Darwin" ]]; then
OS_FAMILY="macOS"
OS_VERSION_ID="0"
OS_CODENAME=""
elif [ -f /etc/os-release ]; then
. /etc/os-release
case "${ID:-}" in
ubuntu|debian) OS_FAMILY="Ubuntu" ;;
centos|rhel|fedora) OS_FAMILY="CentOS" ;;
*) OS_FAMILY="${ID:-Unknown}" ;;
esac
OS_VERSION_ID=$(grep -oP '(?<=^VERSION_ID=")[0-9.]+' /etc/os-release 2>/dev/null | cut -d'.' -f1 || echo "0")
OS_CODENAME=""
if [[ "$OS_FAMILY" == "Ubuntu" ]]; then
OS_CODENAME=$(lsb_release -cs 2>/dev/null || (grep VERSION_CODENAME /etc/os-release 2>/dev/null | cut -d= -f2 | tr -d '"'))
fi
else
OS_FAMILY=$(hostnamectl 2>/dev/null | grep "Operating System" | awk '{print $3}' || echo "Unknown")
OS_VERSION_ID="0"
OS_CODENAME=""
fi
UBUNTU_DOCKER_MIRROR_URL="https://mirrors.ustc.edu.cn/docker-ce"
UBUNTU_DOCKER_GPG_URL="https://mirrors.ustc.edu.cn/docker-ce/linux/ubuntu/gpg"
CENTOS_DOCKER_MIRROR_URL="https://mirrors.tuna.tsinghua.edu.cn/docker-ce"
check_network() {
if curl -sSf --connect-timeout 5 https://download.docker.com/ &>/dev/null; then
echo "✅ 网络连接正常"
return 0
fi
echo "⚠️ 将使用国内镜像源"
return 0
}
check_network
install_docker_macos() {
if ! command -v brew &>/dev/null; then
echo "❌ 未检测到 Homebrew无法自动安装 Docker Desktop。"
echo " 请先安装 Homebrew: https://brew.sh/"
return 1
fi
if brew list --cask docker &>/dev/null; then
echo "✅ Docker Desktop 已安装Homebrew"
else
echo "📦 正在通过 Homebrew 安装 Docker Desktop..."
brew install --cask docker
fi
echo "🚀 正在尝试启动 Docker Desktop..."
open -a Docker || true
echo "⏳ 等待 Docker 引擎就绪(最多 120 秒)..."
local i
for i in $(seq 1 60); do
if docker info &>/dev/null; then
echo "✅ Docker Desktop 已启动并可用"
return 0
fi
sleep 2
done
echo "⚠️ Docker Desktop 已安装,但尚未就绪。"
echo " 请手动打开 Docker Desktop待状态正常后重新运行 ./start.sh"
return 1
}
if command -v docker &>/dev/null; then
echo "✅ Docker 已安装"
else
if [[ "$OS_FAMILY" == "Ubuntu" ]]; then
sudo apt update -y
sudo apt install -y apt-transport-https ca-certificates curl gnupg lsb-release
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL "$UBUNTU_DOCKER_GPG_URL" | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg 2>/dev/null || \
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] $UBUNTU_DOCKER_MIRROR_URL/linux/ubuntu $OS_CODENAME stable" | sudo tee /etc/apt/sources.list.d/docker.list >/dev/null
sudo apt-get update -y
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
elif [[ "$OS_FAMILY" == "CentOS" ]]; then
sudo yum install -y yum-utils
sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
sudo sed -i "s+https://download.docker.com+$CENTOS_DOCKER_MIRROR_URL+g" /etc/yum.repos.d/docker-ce.repo
[[ "$OS_VERSION_ID" -ge "8" ]] && sudo yum module disable -y container-tools 2>/dev/null || true
sudo yum install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
elif [[ "$OS_FAMILY" == "macOS" ]]; then
echo "⚠️ 检测到 macOS未找到 Docker。"
echo "请选择安装方式:"
echo " [1] 自动安装 Docker Desktop需要 Homebrew"
echo " [2] 手动安装(默认)"
read -r -p "请选择 [1/2]: " mac_install_choice
case "${mac_install_choice:-2}" in
1)
if ! install_docker_macos; then
exit 1
fi
;;
2)
echo " 请手动安装 Docker Desktop: https://www.docker.com/products/docker-desktop/"
echo " 安装并启动 Docker Desktop 后,重新运行 ./start.sh"
exit 1
;;
*)
echo "❌ 无效选择"
exit 1
;;
esac
else
echo "❌ 不支持的操作系统: $OS_FAMILY"
exit 1
fi
if [[ "$OS_FAMILY" != "macOS" ]]; then
sudo systemctl enable docker
sudo systemctl start docker
getent group docker | grep -qw "$USER" || sudo usermod -aG docker "$USER"
fi
fi
if docker compose version &>/dev/null; then
echo "✅ Docker Compose 已就绪"
elif command -v docker-compose &>/dev/null; then
echo "✅ Docker Compose (兼容模式) 已就绪"
else
echo "❌ 请安装 docker-compose-plugin"
exit 1
fi
}
# ==================== 启动服务 ====================
start_services() {
echo ""
echo "🚀 启动 EasyAI 服务..."
if docker compose version &>/dev/null; then
sudo docker compose pull && sudo docker compose up -d
else
sudo docker-compose pull && sudo docker-compose up -d
fi
echo "🎉 EasyAI 应用启动成功"
}
# ==================== 执行 HTTPS 配置 ====================
run_https_setup() {
if [ "$DEPLOY_HTTPS" = true ] && [ -n "$DEPLOY_DOMAIN" ]; then
echo ""
echo "🔒 执行 HTTPS 配置(请确保服务器已放行 80、443 端口)..."
if [ -f "./https.sh" ]; then
# https.sh 依赖 easyai-proxy.conf需使用生成的域名配置文件
export EASYAI_PROXY_CONF="${DEPLOY_DOMAIN}.conf"
bash ./https.sh
else
echo "⚠️ 未找到 https.sh请手动配置 HTTPS"
fi
fi
}
# ==================== 主流程 ====================
main() {
init_project_dir
# 检查是否已有 .env 且非强制重新配置
if [ -f .env ] && [ -z "$DEPLOY_FORCE_RECONFIG" ] && [ -z "$DEPLOY_ACCESS" ]; then
echo "📁 检测到已有 .env 配置"
read -r -p "是否重新配置部署方式?[y/N]: " reconfigure
if [[ ! "$reconfigure" =~ ^[yY] ]]; then
echo "⏭️ 使用现有配置继续..."
DEPLOY_MODE="skip"
fi
fi
if [ "$DEPLOY_MODE" != "skip" ]; then
run_deploy_questions
fi
if [ "$DEPLOY_MODE" != "skip" ]; then
setup_env_files
else
if [ ! -f .env.tools ]; then
cp .env.tools.sample .env.tools
fi
if [ ! -f .env.ASG ]; then
cp .env.ASG.sample .env.ASG
fi
if [ ! -f .env.AMS ]; then
cp .env.AMS.sample .env.AMS
fi
fi
if [ "$DEPLOY_DRY_RUN" = "1" ]; then
echo ""
echo "⚠️ dry-run 模式:跳过 Docker 安装和服务启动"
echo " 配置文件已生成,可直接运行 ./start.sh 完成部署"
else
install_docker
start_services
run_https_setup
fi
echo ""
echo "================================"
echo " 部署完成"
echo "================================"
if [ "$DEPLOY_MODE" = "ip" ] && [ -n "$DEPLOY_IP" ]; then
echo "访问地址: http://${DEPLOY_IP}:3010"
elif [ "$DEPLOY_MODE" = "domain" ] && [ -n "$DEPLOY_DOMAIN" ]; then
echo "访问地址: http://${DEPLOY_DOMAIN} (配置 Nginx 后)"
if [ "$DEPLOY_HTTPS" = true ]; then
echo "HTTPS 已启用"
fi
fi
echo ""
}
main "$@"