jshells/install_jsite.sh

781 lines
22 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
# jsite前端自动化部署脚本
# 基于next.js + traefik开发
#
# 后端调用示例:
# 1. 基本部署: ./install_jsite.sh
# 2. 指定项目: ./install_jsite.sh -p myproject -r http://git.example.com/myproject
# 3. 跳过Docker: ./install_jsite.sh --skip-docker
# 4. 跳过Traefik: ./install_jsite.sh --skip-traefik
# 5. 强制更新: ./install_jsite.sh --force-update
# 6. 完整参数: ./install_jsite.sh -p myproject -r http://git.example.com/myproject -n 18 --skip-docker --force-update
#
# 返回码:
# 0 - 成功
# 1 - 参数错误
# 2 - 权限错误
# 3 - 其他错误
set -e # 遇到错误时退出
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# 默认参数
PROJECT_NAME="jingrow"
GIT_REPO="http://git.jingrow.com:3000/jsite/jingrow"
NODE_VERSION="22"
SKIP_DOCKER=false
SKIP_TRAEFIK=false
SKIP_DEPENDENCIES=false
FORCE_UPDATE=false
SKIP_PM2=false
# 端口管理参数
START_PORT=3001
PORT_INCREMENT=1
# .env文件参数
SITE_URL="http://192.168.2.200:3001"
REVALIDATE_TOKEN="535bc122f3e364c"
SITE_NAME="jingrow"
SERVER_URL="https://admin.jingrow.com"
API_KEY="535bc122f3e364c"
API_SECRET="8629a3b12fc1cc2"
# 显示帮助信息
show_help() {
echo "用法: $0 [选项]"
echo ""
echo "选项:"
echo " -p, --project-name NAME 项目名称 (默认: jingrow)"
echo " -r, --repo URL Git仓库地址"
echo " -n, --node-version VERSION Node.js版本 (默认: 22)"
echo " --skip-docker 跳过Docker安装"
echo " --skip-traefik 跳过Traefik安装和启动"
echo " --skip-dependencies 跳过项目依赖安装"
echo " --skip-pm2 跳过PM2安装和启动"
echo " --force-update 强制更新项目(如果已存在)"
echo ""
echo "端口配置:"
echo " --start-port PORT 起始端口 (默认: 3001)"
echo " --port-increment INCREMENT 端口增量 (默认: 1)"
echo ""
echo ".env文件配置:"
echo " --site-url URL 网站URL (默认: http://192.168.2.200:3001)"
echo " --revalidate-token TOKEN 重新验证令牌 (默认: 535bc122f3e364c)"
echo " --site-name NAME 站点名称 (默认: jingrow)"
echo " --server-url URL 服务器URL (默认: https://admin.jingrow.com)"
echo " --api-key KEY API密钥 (默认: 535bc122f3e364c)"
echo " --api-secret SECRET API密钥 (默认: 8629a3b12fc1cc2)"
echo ""
echo " -h, --help 显示此帮助信息"
echo ""
echo "示例:"
echo " $0 -p myproject -r http://git.example.com/myproject"
echo " $0 --skip-docker --skip-traefik"
echo " $0 --force-update"
echo " $0 --site-url http://example.com --site-name myproject"
echo " $0 --start-port 3005 --port-increment 10"
}
# 解析命令行参数
parse_arguments() {
while [[ $# -gt 0 ]]; do
case $1 in
-p|--project-name)
PROJECT_NAME="$2"
shift 2
;;
-r|--repo)
GIT_REPO="$2"
shift 2
;;
-n|--node-version)
NODE_VERSION="$2"
shift 2
;;
--skip-docker)
SKIP_DOCKER=true
shift
;;
--skip-traefik)
SKIP_TRAEFIK=true
shift
;;
--skip-dependencies)
SKIP_DEPENDENCIES=true
shift
;;
--skip-pm2)
SKIP_PM2=true
shift
;;
--force-update)
FORCE_UPDATE=true
shift
;;
--start-port)
START_PORT="$2"
shift 2
;;
--port-increment)
PORT_INCREMENT="$2"
shift 2
;;
--site-url)
SITE_URL="$2"
shift 2
;;
--revalidate-token)
REVALIDATE_TOKEN="$2"
shift 2
;;
--site-name)
SITE_NAME="$2"
shift 2
;;
--server-url)
SERVER_URL="$2"
shift 2
;;
--api-key)
API_KEY="$2"
shift 2
;;
--api-secret)
API_SECRET="$2"
shift 2
;;
-h|--help)
show_help
exit 0
;;
*)
echo -e "${RED}[ERROR]${NC} 未知参数: $1"
show_help
exit 1
;;
esac
done
}
# 日志函数
log_info() {
echo -e "${BLUE}[INFO]${NC} $1"
}
log_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
log_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# 检查是否为root用户
check_root() {
if [[ $EUID -eq 0 ]]; then
log_info "以root用户身份运行脚本"
else
log_error "请以root用户身份运行此脚本"
exit 1
fi
}
# 1. 创建jingrow用户
create_jingrow_user() {
log_info "开始创建jingrow用户..."
if id "jingrow" &>/dev/null; then
log_warning "用户jingrow已存在"
else
useradd -m -s /bin/bash jingrow
log_success "用户jingrow创建成功"
fi
# 确保home目录存在
if [ ! -d "/home/jingrow" ]; then
mkdir -p /home/jingrow
chown jingrow:jingrow /home/jingrow
log_success "创建/home/jingrow目录"
fi
}
# 2. 安装nvm和node.js
install_nodejs() {
log_info "开始安装nvm和node.js..."
# 切换到jingrow用户目录
cd /home/jingrow
# 下载并安装nvm
if [ ! -d "/home/jingrow/.nvm" ]; then
log_info "下载并安装nvm..."
su - jingrow -c "curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash"
log_success "nvm安装完成"
else
log_warning "nvm已存在"
fi
# 加载nvm并安装node.js
log_info "安装Node.js v$NODE_VERSION..."
su - jingrow -c "
export NVM_DIR=\"\$HOME/.nvm\"
[ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\"
nvm install $NODE_VERSION
nvm use $NODE_VERSION
nvm alias default $NODE_VERSION
"
# 验证安装
NODE_VERSION_OUTPUT=$(su - jingrow -c 'export NVM_DIR="$HOME/.nvm" && [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" && node -v')
NPM_VERSION=$(su - jingrow -c 'export NVM_DIR="$HOME/.nvm" && [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" && npm -v')
log_success "Node.js版本: $NODE_VERSION_OUTPUT"
log_success "npm版本: $NPM_VERSION"
}
# 2.5. 安装PM2
install_pm2() {
log_info "开始安装PM2..."
# 检查PM2是否已安装
PM2_VERSION=$(su - jingrow -c 'export NVM_DIR="$HOME/.nvm" && [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" && pm2 -v 2>/dev/null || echo "not_installed"')
if [ "$PM2_VERSION" != "not_installed" ]; then
log_warning "PM2已安装版本: $PM2_VERSION"
else
log_info "安装PM2..."
su - jingrow -c "
export NVM_DIR=\"\$HOME/.nvm\"
[ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\"
npm install -g pm2
"
log_success "PM2安装完成"
fi
# 设置PM2开机自启
su - jingrow -c "
export NVM_DIR=\"\$HOME/.nvm\"
[ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\"
pm2 startup
"
log_success "PM2开机自启配置完成"
}
# 2.6. 端口管理函数
get_available_port() {
local project_name="$1"
local base_port="$START_PORT"
local increment="$PORT_INCREMENT"
# 检查项目是否已有分配的端口
local port_file="/home/jingrow/jsite/port_assignments.json"
if [ -f "$port_file" ]; then
# 如果项目已存在,返回已分配的端口
local existing_port=$(jq -r ".$project_name // empty" "$port_file" 2>/dev/null || echo "")
if [ -n "$existing_port" ] && [ "$existing_port" != "null" ] && [ "$existing_port" != "empty" ]; then
echo "$existing_port"
return 0
fi
fi
# 查找可用端口
local port="$base_port"
local max_attempts=1000
local attempts=0
while [ $attempts -lt $max_attempts ]; do
# 检查端口是否被占用
if ! netstat -tuln 2>/dev/null | grep -q ":$port "; then
# 检查端口是否已被分配
if [ -f "$port_file" ]; then
local used_ports=$(jq -r '.[]' "$port_file" 2>/dev/null || echo "")
if ! echo "$used_ports" | grep -q "^$port$"; then
echo "$port"
return 0
fi
else
echo "$port"
return 0
fi
fi
port=$((port + increment))
attempts=$((attempts + 1))
done
log_error "无法找到可用端口,已尝试 $max_attempts"
return 1
}
# 2.7. 保存端口分配
save_port_assignment() {
local project_name="$1"
local port="$2"
local port_file="/home/jingrow/jsite/port_assignments.json"
# 创建或更新端口分配文件
if [ -f "$port_file" ]; then
# 更新现有文件
jq ". + {\"$project_name\": $port}" "$port_file" > "${port_file}.tmp" && mv "${port_file}.tmp" "$port_file"
else
# 创建新文件
echo "{\"$project_name\": $port}" > "$port_file"
fi
# 设置文件权限
chown jingrow:jingrow "$port_file"
chmod 644 "$port_file"
log_success "端口 $port 已分配给项目 $project_name"
}
# 3. 克隆jsite项目
clone_jsite_project() {
log_info "开始克隆jsite项目: $PROJECT_NAME..."
cd /home/jingrow
# 创建jsite目录如果不存在
if [ ! -d "/home/jingrow/jsite" ]; then
mkdir -p /home/jingrow/jsite
chown jingrow:jingrow /home/jingrow/jsite
log_success "创建jsite目录"
fi
# 检查项目子目录是否已存在
if [ -d "/home/jingrow/jsite/$PROJECT_NAME" ]; then
if [ "$FORCE_UPDATE" = true ]; then
log_warning "项目目录已存在,强制更新..."
rm -rf "/home/jingrow/jsite/$PROJECT_NAME"
cd /home/jingrow/jsite
su - jingrow -c "cd /home/jingrow/jsite && git clone $GIT_REPO $PROJECT_NAME"
log_success "jsite/$PROJECT_NAME项目更新完成"
else
log_warning "jsite/$PROJECT_NAME目录已存在,跳过克隆"
fi
else
cd /home/jingrow/jsite
su - jingrow -c "cd /home/jingrow/jsite && git clone $GIT_REPO $PROJECT_NAME"
log_success "jsite/$PROJECT_NAME项目克隆完成"
fi
}
# 4. 创建.env文件
create_env_file() {
log_info "创建.env文件..."
cd /home/jingrow/jsite/$PROJECT_NAME
# 获取项目端口
local project_port=$(get_available_port "$PROJECT_NAME")
save_port_assignment "$PROJECT_NAME" "$project_port"
# 更新SITE_URL以使用分配的端口
local site_url_with_port=$(echo "$SITE_URL" | sed "s|:[0-9]*|:$project_port|")
# 检查.env文件是否已存在
if [ -f "/home/jingrow/jsite/$PROJECT_NAME/.env" ]; then
if [ "$FORCE_UPDATE" = true ]; then
log_warning ".env文件已存在强制更新..."
else
log_warning ".env文件已存在跳过创建"
return
fi
fi
# 创建.env文件
cat > "/home/jingrow/jsite/$PROJECT_NAME/.env" << EOF
PUBLIC_SITE_URL=$site_url_with_port
REVALIDATE_TOKEN=$REVALIDATE_TOKEN
JINGROW_SITE_NAME=$SITE_NAME
JINGROW_SERVER_URL=$SERVER_URL
JINGROW_API_KEY=$API_KEY
JINGROW_API_SECRET=$API_SECRET
# 项目端口配置
PORT=$project_port
EOF
# 设置文件权限
chown jingrow:jingrow "/home/jingrow/jsite/$PROJECT_NAME/.env"
chmod 600 "/home/jingrow/jsite/$PROJECT_NAME/.env"
log_success ".env文件创建完成 (端口: $project_port)"
}
# 4.5. 创建PM2配置文件
create_pm2_config() {
log_info "创建PM2配置文件..."
cd /home/jingrow/jsite/$PROJECT_NAME
# 获取项目端口
local project_port=$(get_available_port "$PROJECT_NAME")
# 创建logs目录
mkdir -p "/home/jingrow/jsite/$PROJECT_NAME/logs"
# 创建ecosystem.config.js文件
cat > "/home/jingrow/jsite/$PROJECT_NAME/ecosystem.config.js" << EOF
module.exports = {
apps: [{
name: '$PROJECT_NAME',
script: 'npm',
args: 'start',
cwd: '/home/jingrow/jsite/$PROJECT_NAME',
instances: 1,
autorestart: true,
watch: false,
max_memory_restart: '1G',
env: {
NODE_ENV: 'production',
PORT: $project_port
},
env_file: '.env',
log_file: './logs/combined.log',
out_file: './logs/out.log',
error_file: './logs/error.log',
log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
merge_logs: true,
time: true
}]
};
EOF
# 设置文件权限
chown -R jingrow:jingrow "/home/jingrow/jsite/$PROJECT_NAME/ecosystem.config.js"
chown -R jingrow:jingrow "/home/jingrow/jsite/$PROJECT_NAME/logs"
log_success "PM2配置文件创建完成 (端口: $project_port)"
}
# 4.6. 用PM2启动项目
start_project_with_pm2() {
log_info "使用PM2启动项目: $PROJECT_NAME..."
cd /home/jingrow/jsite/$PROJECT_NAME
# 检查项目是否已经在PM2中运行
PM2_STATUS=$(su - jingrow -c "
export NVM_DIR=\"\$HOME/.nvm\"
[ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\"
pm2 list | grep -c '$PROJECT_NAME' || echo '0'
")
if [ "$PM2_STATUS" != "0" ]; then
if [ "$FORCE_UPDATE" = true ]; then
log_warning "项目已在PM2中运行重新启动..."
su - jingrow -c "
export NVM_DIR=\"\$HOME/.nvm\"
[ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\"
cd /home/jingrow/jsite/$PROJECT_NAME
pm2 delete $PROJECT_NAME 2>/dev/null || true
pm2 start ecosystem.config.js
"
else
log_warning "项目已在PM2中运行跳过启动"
return
fi
else
su - jingrow -c "
export NVM_DIR=\"\$HOME/.nvm\"
[ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\"
cd /home/jingrow/jsite/$PROJECT_NAME
pm2 start ecosystem.config.js
"
fi
# 保存PM2配置
su - jingrow -c "
export NVM_DIR=\"\$HOME/.nvm\"
[ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\"
pm2 save
"
# 显示PM2状态
log_info "PM2项目状态"
su - jingrow -c "
export NVM_DIR=\"\$HOME/.nvm\"
[ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\"
pm2 list
"
log_success "项目已使用PM2启动完成"
}
# 5. 安装traefik
install_traefik() {
log_info "开始安装traefik..."
cd /home/jingrow
# 创建traefik目录结构
if [ ! -d "/home/jingrow/traefik" ]; then
mkdir -p /home/jingrow/traefik/conf.d
log_success "创建traefik目录结构"
fi
# 创建acme.json文件
if [ ! -f "/home/jingrow/traefik/acme.json" ]; then
touch /home/jingrow/traefik/acme.json
chmod 600 /home/jingrow/traefik/acme.json
log_success "创建acme.json文件"
fi
# 创建traefik.yml配置文件
cat > /home/jingrow/traefik/traefik.yml << 'EOF'
entryPoints:
web:
address: ":80"
websecure:
address: ":443"
api:
dashboard: true
insecure: true # 生产环境建议关闭
providers:
docker:
exposedByDefault: false
file:
directory: /etc/traefik/conf.d
watch: true
certificatesResolvers:
myresolver:
acme:
email: support@jingrow.com
storage: acme.json
httpChallenge:
entryPoint: web
EOF
log_success "创建traefik.yml配置文件"
# 创建docker-compose.yml文件
cat > /home/jingrow/traefik/docker-compose.yml << 'EOF'
services:
traefik:
# The official v3 Traefik docker image
image: traefik:v3.4
container_name: traefik
restart: always
# Enables the web UI and tells Traefik to listen to docker
command: --api.insecure=true --providers.docker
ports:
# The HTTP port
- "80:80"
- "443:443"
# The Web UI (enabled by --api.insecure=true)
- "8080:8080"
volumes:
# So that Traefik can listen to the Docker events
- /var/run/docker.sock:/var/run/docker.sock
- ./traefik.yml:/etc/traefik/traefik.yml:ro
- ./conf.d:/etc/traefik/conf.d:ro
- ./acme.json:/etc/traefik/acme.json
EOF
log_success "创建docker-compose.yml文件"
# 设置目录权限
chown -R jingrow:jingrow /home/jingrow/traefik
log_success "设置traefik目录权限"
}
# 6. 安装Docker如果未安装
install_docker() {
log_info "检查Docker安装状态..."
if command -v docker &> /dev/null; then
log_warning "Docker已安装"
else
log_info "开始安装Docker..."
# 更新包索引
apt-get update
# 安装必要的包
apt-get install -y apt-transport-https ca-certificates curl gnupg lsb-release
# 添加Docker官方GPG密钥
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
# 设置稳定版仓库
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
# 更新包索引
apt-get update
# 安装Docker Engine
apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
# 启动Docker服务
systemctl start docker
systemctl enable docker
log_success "Docker安装完成"
fi
# 将jingrow用户添加到docker组
usermod -aG docker jingrow
log_success "将jingrow用户添加到docker组"
}
# 7. 启动traefik
start_traefik() {
log_info "启动traefik服务..."
cd /home/jingrow/traefik
# 使用jingrow用户启动traefik
su - jingrow -c "cd /home/jingrow/traefik && docker compose up -d"
log_success "traefik服务启动完成"
}
# 8. 安装项目依赖
install_project_dependencies() {
log_info "安装jsite/$PROJECT_NAME项目依赖..."
cd /home/jingrow/jsite/$PROJECT_NAME
su - jingrow -c "
export NVM_DIR=\"\$HOME/.nvm\"
[ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\"
cd /home/jingrow/jsite/$PROJECT_NAME
npm install
"
log_success "项目依赖安装完成"
}
# 9. 显示部署信息
show_deployment_info() {
log_success "=== 部署完成 ==="
echo ""
log_info "部署信息:"
echo " - 用户: jingrow"
echo " - jsite目录: /home/jingrow/jsite"
echo " - 项目目录: /home/jingrow/jsite/$PROJECT_NAME"
echo " - 项目端口: $(get_available_port "$PROJECT_NAME")"
echo " - Traefik目录: /home/jingrow/traefik"
echo " - Traefik管理界面: http://localhost:8080"
echo " - PM2配置文件: /home/jingrow/jsite/$PROJECT_NAME/ecosystem.config.js"
echo " - PM2日志目录: /home/jingrow/jsite/$PROJECT_NAME/logs"
echo ""
log_info ".env文件配置"
echo " - PUBLIC_SITE_URL: $SITE_URL"
echo " - JINGROW_SITE_NAME: $SITE_NAME"
echo " - JINGROW_SERVER_URL: $SERVER_URL"
echo " - REVALIDATE_TOKEN: $REVALIDATE_TOKEN"
echo " - JINGROW_API_KEY: $API_KEY"
echo " - JINGROW_API_SECRET: $API_SECRET"
echo ""
log_info "PM2管理命令"
echo " - 查看状态: pm2 list"
echo " - 查看日志: pm2 logs $PROJECT_NAME"
echo " - 重启项目: pm2 restart $PROJECT_NAME"
echo " - 停止项目: pm2 stop $PROJECT_NAME"
echo " - 删除项目: pm2 delete $PROJECT_NAME"
echo " - 监控界面: pm2 monit"
echo ""
log_info "下一步操作:"
echo " 1. 进入项目目录: cd /home/jingrow/jsite/$PROJECT_NAME"
echo " 2. 查看PM2状态: pm2 list"
echo " 3. 查看项目日志: pm2 logs $PROJECT_NAME"
echo " 4. 访问Traefik管理界面: http://localhost:8080"
echo " 5. 访问项目: $SITE_URL"
echo ""
log_warning "注意请确保防火墙允许80、443、8080端口访问"
}
# 主函数
main() {
log_info "开始jsite前端自动化部署..."
echo ""
log_info "部署参数:"
echo " - 项目名称: $PROJECT_NAME"
echo " - Git仓库: $GIT_REPO"
echo " - Node.js版本: $NODE_VERSION"
echo " - 跳过Docker: $SKIP_DOCKER"
echo " - 跳过Traefik: $SKIP_TRAEFIK"
echo " - 跳过依赖安装: $SKIP_DEPENDENCIES"
echo " - 跳过PM2: $SKIP_PM2"
echo " - 强制更新: $FORCE_UPDATE"
echo ""
log_info "端口配置:"
echo " - 起始端口: $START_PORT"
echo " - 端口增量: $PORT_INCREMENT"
echo ""
log_info ".env文件参数:"
echo " - 网站URL: $SITE_URL"
echo " - 站点名称: $SITE_NAME"
echo " - 服务器URL: $SERVER_URL"
echo " - 重新验证令牌: $REVALIDATE_TOKEN"
echo " - API密钥: $API_KEY"
echo " - API密钥: $API_SECRET"
echo ""
check_root
create_jingrow_user
if [ "$SKIP_DOCKER" = false ]; then
install_docker
else
log_warning "跳过Docker安装"
fi
install_nodejs
if [ "$SKIP_PM2" = false ]; then
install_pm2
else
log_warning "跳过PM2安装"
fi
clone_jsite_project
create_env_file
if [ "$SKIP_DEPENDENCIES" = false ]; then
install_project_dependencies
else
log_warning "跳过项目依赖安装"
fi
if [ "$SKIP_PM2" = false ]; then
create_pm2_config
start_project_with_pm2
else
log_warning "跳过PM2配置和启动"
fi
if [ "$SKIP_TRAEFIK" = false ]; then
install_traefik
start_traefik
else
log_warning "跳过Traefik安装和启动"
fi
show_deployment_info
log_success "部署脚本执行完成!"
}
# 执行主函数
parse_arguments "$@"
main "$@"