增加jsite管理脚本用于新建删除构建启动重启jsite实例

This commit is contained in:
jingrow 2025-08-09 17:06:42 +08:00
parent 46277dfdb4
commit bcab28d97f

771
jsite.sh Normal file
View File

@ -0,0 +1,771 @@
#!/bin/bash
# ========================================
# jsite网站管理脚本
# 功能新建、删除、构建、启动、停止、重启jsite网站
# ========================================
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# ========================================
# 默认配置
# ========================================
# 基础配置
DEFAULT_GIT_REPO="http://git.jingrow.com:3000/jsite/jingrow"
JSITE_BASE_DIR="/home/jingrow/jsite"
USER_NAME="jingrow"
TRAEFIK_CONFIG_DIR="/home/jingrow/traefik-docker/conf.d/website"
# 默认配置参数
DEFAULT_SITE_URL="example.com"
START_PORT=3001
PORT_INCREMENT=1
# 网络配置
PUBLIC_IP="" # 公网IP地址 (用于内网IP不可用时)
# ========================================
# 日志函数
# ========================================
log_info() {
echo -e "${BLUE}[INFO]${NC} $1"
}
log_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
log_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1" >&2
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1" >&2
}
# ========================================
# 工具函数
# ========================================
# 检查用户是否存在
check_user() {
if ! id "$USER_NAME" &>/dev/null; then
log_error "用户 $USER_NAME 不存在,请先创建用户"
exit 1
fi
}
# 检查jsite基础目录
check_base_dir() {
if [ ! -d "$JSITE_BASE_DIR" ]; then
log_info "创建jsite基础目录: $JSITE_BASE_DIR"
mkdir -p "$JSITE_BASE_DIR"
chown "$USER_NAME:$USER_NAME" "$JSITE_BASE_DIR"
fi
}
# 检查网站目录是否存在
check_site_exists() {
local site_name="$1"
[ -d "$JSITE_BASE_DIR/$site_name" ]
}
# 获取网站状态
get_site_status() {
local site_name="$1"
if ! check_site_exists "$site_name"; then
echo "not_exists"
return
fi
# 检查PM2进程状态
local pm2_status=$(su - "$USER_NAME" -c "
export NVM_DIR=\"\$HOME/.nvm\"
[ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\"
pm2 list 2>/dev/null | grep -w '$site_name' | grep -c 'online' || echo '0'
")
if [ "$pm2_status" != "0" ]; then
echo "running"
else
echo "stopped"
fi
}
# 智能IP选择优先使用内网IP没有内网IP时使用公网IP
get_optimal_host_ip() {
local host_ip=""
# 方法1: 尝试获取内网IP
local private_ip=$(ip route get 8.8.8.8 2>/dev/null | awk '{print $7}' | head -1)
if [ -n "$private_ip" ] && [ "$private_ip" != "8.8.8.8" ]; then
# 测试内网IP是否可达
if ping -c 2 -W 1 "$private_ip" >/dev/null 2>&1; then
host_ip="$private_ip"
# 注意:这里不输出日志,避免污染返回值
else
# 注意:这里不输出日志,避免污染返回值
:
fi
fi
# 方法2: 如果没有内网IP或内网IP不可达使用公网IP
if [ -z "$host_ip" ]; then
if [ -n "$PUBLIC_IP" ]; then
host_ip="$PUBLIC_IP"
# 注意:这里不输出日志,避免污染返回值
else
# 尝试自动获取公网IP
local auto_public_ip=$(curl -s --max-time 5 ifconfig.me 2>/dev/null || curl -s --max-time 5 ipinfo.io/ip 2>/dev/null)
if [ -n "$auto_public_ip" ]; then
host_ip="$auto_public_ip"
# 注意:这里不输出日志,避免污染返回值
else
# 最后回退使用第一个可用IP
host_ip=$(hostname -I | awk '{print $1}')
# 注意:这里不输出日志,避免污染返回值
fi
fi
fi
echo "$host_ip"
}
# 显示帮助信息
show_help() {
echo "用法: $0 <命令> <网站名称> [选项]"
echo ""
echo "命令:"
echo " create <name> [git-repo] [domain] 新建网站"
echo " delete <name> 删除网站"
echo " build <name> 构建网站"
echo " start <name> 启动网站"
echo " stop <name> 停止网站"
echo " restart <name> 重启网站"
echo " status <name> 查看网站状态"
echo " list 列出所有网站"
echo " logs <name> 查看网站日志"
echo ""
echo "选项:"
echo " -h, --help 显示此帮助信息"
echo ""
echo "示例:"
echo " $0 create mysite"
echo " $0 create mysite http://git.example.com/myproject"
echo " $0 create mysite http://git.example.com/myproject mysite.com"
echo " $0 start mysite"
echo " $0 stop mysite"
echo " $0 restart mysite"
echo " $0 status mysite"
echo " $0 list"
}
# 获取可用端口
get_available_port() {
local site_name="$1"
local port_file="$JSITE_BASE_DIR/site_ports.json"
# 如果端口文件不存在,返回起始端口
if [ ! -f "$port_file" ]; then
echo "$START_PORT"
return 0
fi
# 检查网站是否已有分配的端口
if command -v jq &> /dev/null; then
local existing_port=$(jq -r ".$site_name // empty" "$port_file" 2>/dev/null)
if [ -n "$existing_port" ] && [ "$existing_port" != "null" ] && [ "$existing_port" != "empty" ]; then
echo "$existing_port"
return 0
fi
# 找到最大端口号并加1
local max_port=$(jq -r 'max(.[])' "$port_file" 2>/dev/null || echo "$START_PORT")
if [ -n "$max_port" ] && [ "$max_port" != "null" ]; then
echo $((max_port + PORT_INCREMENT))
else
echo "$START_PORT"
fi
else
# 如果没有jq使用简单逻辑
echo "$START_PORT"
fi
}
# 保存端口分配
save_port_assignment() {
local site_name="$1"
local port="$2"
local port_file="$JSITE_BASE_DIR/site_ports.json"
if command -v jq &> /dev/null; then
if [ -f "$port_file" ]; then
jq ". + {\"$site_name\": $port}" "$port_file" > "${port_file}.tmp" && mv "${port_file}.tmp" "$port_file"
else
echo "{\"$site_name\": $port}" > "$port_file"
fi
else
# 简单的JSON格式
if [ -f "$port_file" ]; then
grep -v "\"$site_name\"" "$port_file" > "${port_file}.tmp" 2>/dev/null || true
sed -i "s/}$/,\"$site_name\": $port}/" "${port_file}.tmp" 2>/dev/null || echo "{\"$site_name\": $port}" > "${port_file}.tmp"
mv "${port_file}.tmp" "$port_file"
else
echo "{\"$site_name\": $port}" > "$port_file"
fi
fi
chown "$USER_NAME:$USER_NAME" "$port_file" 2>/dev/null || true
}
# 生成Host规则
generate_host_rule() {
local domain="$1"
# 移除协议前缀
domain=$(echo "$domain" | sed -E 's|^https?://||')
# 移除端口号
domain=$(echo "$domain" | sed -E 's|:[0-9]+$||')
# 检查是否为一级域名
if [[ "$domain" =~ ^[^.]+\.[^.]+$ ]]; then
echo "Host(\`$domain\`) || Host(\`www.$domain\`)"
else
echo "Host(\`$domain\`)"
fi
}
# 创建traefik网站配置文件
create_traefik_config() {
local site_name="$1"
local domain="$2"
local port="$3"
# 检查traefik配置目录是否存在
if [ ! -d "$TRAEFIK_CONFIG_DIR" ]; then
log_warning "Traefik配置目录不存在: $TRAEFIK_CONFIG_DIR"
log_info "请先确保Traefik已正确安装"
return 1
fi
local config_file="$TRAEFIK_CONFIG_DIR/$site_name.yml"
local host_rule=$(generate_host_rule "$domain")
# 获取本机IP地址
local host_ip=$(get_optimal_host_ip)
# 创建配置文件
cat > "$config_file" << EOF
http:
routers:
main-https:
rule: &host_rule $host_rule
entryPoints:
- websecure
service: main-service
tls:
certResolver: myresolver
main-http-redirect:
rule: *host_rule
entryPoints:
- web
middlewares:
- redirect-to-https
service: noop
services:
main-service:
loadBalancer:
servers:
- url: "http://$host_ip:$port"
noop:
loadBalancer:
servers:
- url: "http://127.0.0.1:65535" # 占位用无实际后端仅用于HTTP跳转
middlewares:
redirect-to-https:
redirectScheme:
scheme: https
permanent: true
EOF
chown "$USER_NAME:$USER_NAME" "$config_file" 2>/dev/null || true
chmod 644 "$config_file"
log_success "Traefik配置文件已创建: $config_file"
log_info "域名: $domain -> $host_ip:$port"
}
# 删除traefik网站配置文件
remove_traefik_config() {
local site_name="$1"
local config_file="$TRAEFIK_CONFIG_DIR/$site_name.yml"
if [ -f "$config_file" ]; then
rm -f "$config_file"
log_success "Traefik配置文件已删除: $config_file"
fi
}
# 重启traefik服务以加载新配置
restart_traefik() {
local traefik_dir="/home/jingrow/traefik-docker"
if [ -d "$traefik_dir" ] && [ -f "$traefik_dir/docker-compose.yml" ]; then
log_info "重启Traefik服务以加载新配置..."
su - "$USER_NAME" -c "cd '$traefik_dir' && docker compose restart" 2>/dev/null || {
log_warning "无法重启Traefik服务请手动重启"
}
fi
}
# ========================================
# 核心功能函数
# ========================================
# 新建网站
create_site() {
local site_name="$1"
local git_repo="${2:-$DEFAULT_GIT_REPO}"
local domain="${3:-$DEFAULT_SITE_URL}"
if [ -z "$site_name" ]; then
log_error "请指定网站名称"
return 1
fi
log_info "开始创建网站: $site_name"
# 检查网站是否已存在
if check_site_exists "$site_name"; then
log_error "网站 $site_name 已存在"
return 1
fi
# 获取并分配端口
local port=$(get_available_port "$site_name")
# 克隆项目
log_info "克隆项目: $git_repo"
if ! su - "$USER_NAME" -c "cd '$JSITE_BASE_DIR' && git clone '$git_repo' '$site_name'"; then
log_error "项目克隆失败"
return 1
fi
# 检查package.json是否存在
if [ ! -f "$JSITE_BASE_DIR/$site_name/package.json" ]; then
log_error "package.json文件不存在这可能不是一个有效的Node.js项目"
rm -rf "$JSITE_BASE_DIR/$site_name"
return 1
fi
# 保存端口分配
save_port_assignment "$site_name" "$port"
# 创建traefik配置文件
if create_traefik_config "$site_name" "$domain" "$port"; then
restart_traefik
fi
log_success "网站 $site_name 创建成功"
log_info "项目路径: $JSITE_BASE_DIR/$site_name"
log_info "分配端口: $port"
log_info "绑定域名: $domain"
# 提示下一步操作
log_info "下一步操作:"
echo " 1. 构建项目: $0 build $site_name"
echo " 2. 启动项目: $0 start $site_name"
}
# 删除网站
delete_site() {
local site_name="$1"
if [ -z "$site_name" ]; then
log_error "请指定网站名称"
return 1
fi
if ! check_site_exists "$site_name"; then
log_error "网站 $site_name 不存在"
return 1
fi
log_warning "即将删除网站: $site_name"
read -p "确认删除?(y/N): " confirm
if [ "$confirm" != "y" ] && [ "$confirm" != "Y" ]; then
log_info "取消删除操作"
return 0
fi
# 停止PM2进程
local status=$(get_site_status "$site_name")
if [ "$status" = "running" ]; then
log_info "停止运行中的进程..."
stop_site "$site_name"
fi
# 删除PM2配置
su - "$USER_NAME" -c "
export NVM_DIR=\"\$HOME/.nvm\"
[ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\"
pm2 delete '$site_name' 2>/dev/null || true
"
# 删除traefik配置文件
remove_traefik_config "$site_name"
# 删除端口分配记录
local port_file="$JSITE_BASE_DIR/site_ports.json"
if [ -f "$port_file" ] && command -v jq &> /dev/null; then
jq "del(.$site_name)" "$port_file" > "${port_file}.tmp" && mv "${port_file}.tmp" "$port_file"
chown "$USER_NAME:$USER_NAME" "$port_file" 2>/dev/null || true
fi
# 删除项目目录
log_info "删除项目目录: $JSITE_BASE_DIR/$site_name"
rm -rf "$JSITE_BASE_DIR/$site_name"
# 重启traefik以移除配置
restart_traefik
log_success "网站 $site_name 已删除"
}
# 构建网站
build_site() {
local site_name="$1"
if [ -z "$site_name" ]; then
log_error "请指定网站名称"
return 1
fi
if ! check_site_exists "$site_name"; then
log_error "网站 $site_name 不存在"
return 1
fi
log_info "构建网站: $site_name"
# 安装依赖
log_info "安装依赖..."
if ! su - "$USER_NAME" -c "
export NVM_DIR=\"\$HOME/.nvm\"
[ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\"
cd '$JSITE_BASE_DIR/$site_name'
npm install
"; then
log_error "依赖安装失败"
return 1
fi
# 构建项目
log_info "构建项目..."
if ! su - "$USER_NAME" -c "
export NVM_DIR=\"\$HOME/.nvm\"
[ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\"
cd '$JSITE_BASE_DIR/$site_name'
npm run build
"; then
log_error "项目构建失败"
return 1
fi
log_success "网站 $site_name 构建完成"
}
# 启动网站
start_site() {
local site_name="$1"
if [ -z "$site_name" ]; then
log_error "请指定网站名称"
return 1
fi
if ! check_site_exists "$site_name"; then
log_error "网站 $site_name 不存在"
return 1
fi
local status=$(get_site_status "$site_name")
if [ "$status" = "running" ]; then
log_warning "网站 $site_name 已在运行中"
return 0
fi
# 获取分配的端口
local port=$(get_available_port "$site_name")
log_info "启动网站: $site_name (端口: $port)"
# 检查是否有构建产物
if [ ! -d "$JSITE_BASE_DIR/$site_name/.next" ] && [ ! -d "$JSITE_BASE_DIR/$site_name/dist" ] && [ ! -d "$JSITE_BASE_DIR/$site_name/build" ]; then
log_warning "未找到构建产物,建议先执行构建: $0 build $site_name"
fi
# 使用PM2启动设置端口环境变量
if ! su - "$USER_NAME" -c "
export NVM_DIR=\"\$HOME/.nvm\"
[ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\"
cd '$JSITE_BASE_DIR/$site_name'
PORT=$port pm2 start npm --name '$site_name' -- start
"; then
log_error "网站启动失败"
return 1
fi
# 等待启动完成
sleep 3
local new_status=$(get_site_status "$site_name")
if [ "$new_status" = "running" ]; then
log_success "网站 $site_name 启动成功 (端口: $port)"
else
log_error "网站 $site_name 启动失败"
return 1
fi
}
# 停止网站
stop_site() {
local site_name="$1"
if [ -z "$site_name" ]; then
log_error "请指定网站名称"
return 1
fi
local status=$(get_site_status "$site_name")
if [ "$status" = "not_exists" ]; then
log_error "网站 $site_name 不存在"
return 1
fi
if [ "$status" = "stopped" ]; then
log_warning "网站 $site_name 已停止"
return 0
fi
log_info "停止网站: $site_name"
if ! su - "$USER_NAME" -c "
export NVM_DIR=\"\$HOME/.nvm\"
[ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\"
pm2 stop '$site_name'
"; then
log_error "网站停止失败"
return 1
fi
log_success "网站 $site_name 已停止"
}
# 重启网站
restart_site() {
local site_name="$1"
if [ -z "$site_name" ]; then
log_error "请指定网站名称"
return 1
fi
local status=$(get_site_status "$site_name")
if [ "$status" = "not_exists" ]; then
log_error "网站 $site_name 不存在"
return 1
fi
log_info "重启网站: $site_name"
if [ "$status" = "running" ]; then
if ! su - "$USER_NAME" -c "
export NVM_DIR=\"\$HOME/.nvm\"
[ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\"
pm2 restart '$site_name'
"; then
log_error "网站重启失败"
return 1
fi
else
# 如果未运行,则启动
start_site "$site_name"
return $?
fi
log_success "网站 $site_name 重启成功"
}
# 查看网站状态
show_site_status() {
local site_name="$1"
if [ -z "$site_name" ]; then
log_error "请指定网站名称"
return 1
fi
local status=$(get_site_status "$site_name")
case "$status" in
"not_exists")
log_error "网站 $site_name 不存在"
return 1
;;
"running")
log_success "网站 $site_name 正在运行"
;;
"stopped")
log_warning "网站 $site_name 已停止"
;;
esac
# 显示PM2详细状态
if [ "$status" != "not_exists" ]; then
log_info "PM2状态:"
su - "$USER_NAME" -c "
export NVM_DIR=\"\$HOME/.nvm\"
[ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\"
pm2 list | grep -E '(App name|$site_name)' || echo '未找到PM2进程'
"
fi
}
# 列出所有网站
list_sites() {
log_info "jsite网站列表:"
if [ ! -d "$JSITE_BASE_DIR" ] || [ -z "$(ls -A "$JSITE_BASE_DIR" 2>/dev/null)" ]; then
echo " 暂无网站"
return 0
fi
for site_dir in "$JSITE_BASE_DIR"/*; do
if [ -d "$site_dir" ]; then
local site_name=$(basename "$site_dir")
local status=$(get_site_status "$site_name")
case "$status" in
"running")
echo " - $site_name (运行中)"
;;
"stopped")
echo " - $site_name (已停止)"
;;
*)
echo " - $site_name (未知状态)"
;;
esac
fi
done
}
# 查看网站日志
show_site_logs() {
local site_name="$1"
if [ -z "$site_name" ]; then
log_error "请指定网站名称"
return 1
fi
local status=$(get_site_status "$site_name")
if [ "$status" = "not_exists" ]; then
log_error "网站 $site_name 不存在"
return 1
fi
log_info "网站 $site_name 的日志:"
su - "$USER_NAME" -c "
export NVM_DIR=\"\$HOME/.nvm\"
[ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\"
pm2 logs '$site_name'
"
}
# ========================================
# 主程序
# ========================================
main() {
# 检查参数
if [ $# -eq 0 ]; then
show_help
exit 1
fi
# 处理帮助选项
case "$1" in
-h|--help)
show_help
exit 0
;;
esac
# 检查用户和基础目录
check_user
check_base_dir
# 解析命令
local command="$1"
local site_name="$2"
local extra_param="$3"
local fourth_param="$4"
case "$command" in
create)
# create命令支持第三个参数作为域名
if [ -n "$fourth_param" ]; then
create_site "$site_name" "$extra_param" "$fourth_param"
else
create_site "$site_name" "$extra_param"
fi
;;
delete)
delete_site "$site_name"
;;
build)
build_site "$site_name"
;;
start)
start_site "$site_name"
;;
stop)
stop_site "$site_name"
;;
restart)
restart_site "$site_name"
;;
status)
show_site_status "$site_name"
;;
list)
list_sites
;;
logs)
show_site_logs "$site_name"
;;
*)
log_error "未知命令: $command"
echo ""
show_help
exit 1
;;
esac
}
# 执行主函数
main "$@"