flnxi-Wu/scripts/tools/install.sh
WuYongJie 542c7bcc49 feat: 优化自定义上传模板系统
- 修复内置模板验证问题(builtin-video-material等)
- 添加模板验证工具 lib/template-validator.ts
- 创建模板系统优化需求文档(30个详细需求)
- 优化上传API的模板验证逻辑
- 改进错误提示信息的清晰度
- 添加字段池和分类管理相关需求
- 完善自定义字段的创建、验证、存储逻辑
- 清理过期的 .specstory 和 .trae 文件
2025-12-13 14:26:43 +08:00

500 lines
18 KiB
Bash
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
# ============================================
# FLNXI素材库 - 一键安装脚本
# ============================================
set -e
echo "========================================"
echo " FLNXI素材库 - 一键安装向导"
echo "========================================"
echo ""
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# 检查是否为root用户
if [ "$EUID" -eq 0 ]; then
echo -e "${YELLOW}警告: 不建议使用root用户运行此脚本${NC}"
read -p "是否继续? (y/n): " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
exit 1
fi
fi
# 检查 Docker 是否安装
echo -e "${BLUE}[1/8]${NC} 检查 Docker 环境..."
if ! command -v docker &> /dev/null; then
echo -e "${RED}错误: 未检测到 Docker,请先安装 Docker${NC}"
echo "安装指南: https://docs.docker.com/engine/install/"
exit 1
fi
if ! command -v docker-compose &> /dev/null && ! docker compose version &> /dev/null; then
echo -e "${RED}错误: 未检测到 Docker Compose,请先安装 Docker Compose${NC}"
echo "安装指南: https://docs.docker.com/compose/install/"
exit 1
fi
# 检查 Docker 权限(自动处理)
echo "检查 Docker 权限..."
USE_SUDO=""
if ! docker ps &> /dev/null; then
# 检测到权限问题,自动使用 sudo
if command -v sudo &> /dev/null; then
echo -e "${YELLOW}检测到 Docker 权限问题,将自动使用 sudo 运行 Docker 命令${NC}"
USE_SUDO="sudo"
else
echo -e "${RED}错误: 无法访问 Docker 且未找到 sudo 命令${NC}"
echo "请使用 root 用户运行此脚本,或联系管理员配置 Docker 权限"
exit 1
fi
fi
# 更新 docker 和 docker-compose 命令
if [ -n "$USE_SUDO" ]; then
DOCKER_CMD="sudo docker"
# 检测 docker-compose 命令
if sudo docker compose version &> /dev/null; then
DOCKER_COMPOSE="sudo docker compose"
else
DOCKER_COMPOSE="sudo docker-compose"
fi
else
DOCKER_CMD="docker"
# 检测 docker-compose 命令
if docker compose version &> /dev/null; then
DOCKER_COMPOSE="docker compose"
else
DOCKER_COMPOSE="docker-compose"
fi
fi
# 验证 Docker 是否可用
if ! $DOCKER_CMD ps &> /dev/null; then
echo -e "${RED}错误: 无法访问 Docker,请检查权限或使用 root 用户运行${NC}"
exit 1
fi
echo -e "${GREEN}✓ Docker 环境检查通过${NC}"
echo -e "${GREEN}✓ 使用命令: $DOCKER_COMPOSE${NC}"
# 创建 .env.production 文件
echo ""
echo -e "${BLUE}[2/8]${NC} 配置环境变量..."
if [ ! -f .env.production ]; then
# 自动检测服务器IP
echo "正在自动检测服务器IP地址..."
# 尝试多种方法获取IP
SERVER_IP=""
# 方法1: 通过 ip route 获取
if command -v ip &> /dev/null; then
SERVER_IP=$(ip route get 1 2>/dev/null | awk '{print $7; exit}')
fi
# 方法2: 通过 hostname 获取
if [ -z "$SERVER_IP" ] && command -v hostname &> /dev/null; then
SERVER_IP=$(hostname -I 2>/dev/null | awk '{print $1}')
fi
# 方法3: 通过 ifconfig 获取
if [ -z "$SERVER_IP" ] && command -v ifconfig &> /dev/null; then
SERVER_IP=$(ifconfig 2>/dev/null | grep "inet " | grep -v 127.0.0.1 | awk '{print $2}' | head -n 1)
fi
# 如果自动检测失败,提示用户输入
if [ -z "$SERVER_IP" ]; then
echo -e "${YELLOW}无法自动检测IP地址${NC}"
read -p "请输入服务器IP地址: " SERVER_IP
else
echo -e "${GREEN}检测到服务器IP: $SERVER_IP${NC}"
read -p "是否使用此IP? (Y/n): " -n 1 -r
echo
if [[ $REPLY =~ ^[Nn]$ ]]; then
read -p "请输入服务器IP地址: " SERVER_IP
fi
fi
# 询问端口配置
echo ""
echo "端口配置 (直接回车使用默认值):"
read -p "应用端口 [4320]: " APP_PORT
APP_PORT=${APP_PORT:-4320}
read -p "MinIO API端口 [9000]: " MINIO_API_PORT
MINIO_API_PORT=${MINIO_API_PORT:-9000}
read -p "MinIO控制台端口 [9001]: " MINIO_CONSOLE_PORT
MINIO_CONSOLE_PORT=${MINIO_CONSOLE_PORT:-9001}
# 生成随机密码
echo ""
echo "生成安全密码..."
POSTGRES_PASSWORD=$(openssl rand -base64 16 2>/dev/null || cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 16 | head -n 1)
MINIO_ROOT_PASSWORD=$(openssl rand -base64 16 2>/dev/null || cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 16 | head -n 1)
NEXTAUTH_SECRET=$(openssl rand -base64 32 2>/dev/null || cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1)
# 询问初始管理员账号
echo ""
echo "配置初始管理员账号 (首次登录使用):"
# 固定使用 admin@flnxi.com 和 88888888,不可更改
INIT_ADMIN_EMAIL="admin@flnxi.com"
INIT_ADMIN_PASSWORD="88888888"
echo -e "${GREEN}✓ 初始管理员账号已固定配置${NC}"
echo " - 邮箱: $INIT_ADMIN_EMAIL"
echo " - 密码: $INIT_ADMIN_PASSWORD"
echo -e "${YELLOW}提示: 首次登录后系统将强制要求修改密码${NC}"
# 选择部署环境
echo ""
echo "选择部署环境:"
echo " 1) 测试环境 (test) - 使用测试镜像标签(需要登录)"
echo " 2) 正式环境 (prod) - 使用正式镜像标签(公开,无需登录)"
read -p "请选择 [1/2,默认: 2]: " ENV_CHOICE
ENV_CHOICE=${ENV_CHOICE:-2}
if [ "$ENV_CHOICE" = "1" ]; then
IMAGE_ENV="test"
IMAGE_TAG="test"
HARBOR_PROJECT="flnxi" # 使用同一个项目
echo -e "${YELLOW}已选择: 测试环境(需要 Harbor 登录)${NC}"
# 测试环境需要登录 Harbor(因为测试标签可能是私有的)
echo ""
echo "Harbor 镜像仓库配置(测试环境需要登录):"
read -p "Harbor 地址 [harbor.flnxi.com]: " HARBOR_URL
HARBOR_URL=${HARBOR_URL:-harbor.flnxi.com}
read -p "Harbor 用户名: " HARBOR_USERNAME
read -sp "Harbor 密码: " HARBOR_PASSWORD
echo
else
IMAGE_ENV="prod"
IMAGE_TAG="latest"
HARBOR_PROJECT="flnxi" # 使用同一个项目
echo -e "${GREEN}已选择: 正式环境(公开镜像,无需登录)${NC}"
# 正式环境使用公开镜像,无需登录
HARBOR_URL="harbor.flnxi.com"
HARBOR_USERNAME=""
HARBOR_PASSWORD=""
fi
# URL 编码密码(处理特殊字符)
# 使用 Python 或 Perl 进行 URL 编码,如果没有则使用 sed 简单替换
if command -v python3 &> /dev/null; then
POSTGRES_PASSWORD_ENCODED=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$POSTGRES_PASSWORD', safe=''))")
elif command -v python &> /dev/null; then
POSTGRES_PASSWORD_ENCODED=$(python -c "import urllib.parse; print(urllib.parse.quote('$POSTGRES_PASSWORD', safe=''))")
elif command -v perl &> /dev/null; then
POSTGRES_PASSWORD_ENCODED=$(perl -MURI::Escape -e "print uri_escape('$POSTGRES_PASSWORD');")
else
# 如果没有 Python 或 Perl,使用 sed 简单替换常见特殊字符
POSTGRES_PASSWORD_ENCODED=$(echo "$POSTGRES_PASSWORD" | sed 's/+/%2B/g; s/\//%2F/g; s/=/%3D/g; s/@/%40/g; s/:/%3A/g')
fi
# 生成完整的 DATABASE_URL(密码已编码)
DATABASE_URL="postgresql://postgres:${POSTGRES_PASSWORD_ENCODED}@postgres:5432/flnxi"
# 创建 .env.production 文件(客户安装使用)
cat > .env.production << EOF
# FLNXI素材库 - 生产环境配置
# 生成时间: $(date)
# 服务器配置
SERVER_IP=$SERVER_IP
APP_PORT=$APP_PORT
# 数据库配置
POSTGRES_USER=postgres
POSTGRES_PASSWORD=$POSTGRES_PASSWORD
POSTGRES_DB=flnxi
# 完整的数据库连接URL(密码已URL编码,可直接使用)
DATABASE_URL=$DATABASE_URL
# MinIO 配置
MINIO_ROOT_USER=minioadmin
MINIO_ROOT_PASSWORD=$MINIO_ROOT_PASSWORD
MINIO_API_PORT=$MINIO_API_PORT
MINIO_CONSOLE_PORT=$MINIO_CONSOLE_PORT
S3_BUCKET=flnxi-materials
# 应用安全配置
NEXTAUTH_SECRET=$NEXTAUTH_SECRET
NEXTAUTH_URL=http://$SERVER_IP:$APP_PORT
APP_PUBLIC_URL=http://$SERVER_IP:$APP_PORT
# 初始管理员账号
INIT_ADMIN_EMAIL=$INIT_ADMIN_EMAIL
INIT_ADMIN_PASSWORD=$INIT_ADMIN_PASSWORD
# 镜像环境配置
IMAGE_ENV=$IMAGE_ENV
IMAGE_TAG=$IMAGE_TAG
HARBOR_PROJECT=$HARBOR_PROJECT
DOCKER_REGISTRY=harbor.flnxi.com/$HARBOR_PROJECT
# Harbor 配置(如果配置了)
EOF
if [ -n "$HARBOR_USERNAME" ] && [ -n "$HARBOR_PASSWORD" ]; then
cat >> .env.production << EOF
HARBOR_URL=$HARBOR_URL
HARBOR_USERNAME=$HARBOR_USERNAME
HARBOR_PASSWORD=$HARBOR_PASSWORD
EOF
fi
# 同步一份 .env(供 docker compose 直接读取,无需客户手动改名)
cp .env.production .env
echo -e "${GREEN}✓ 环境配置文件已创建${NC}"
echo ""
echo -e "${YELLOW}重要信息 (请保存):${NC}"
echo "----------------------------------------"
echo "服务器IP: $SERVER_IP"
echo "应用端口: $APP_PORT"
echo "应用地址: http://$SERVER_IP:$APP_PORT"
echo ""
echo "初始管理员账号:"
echo " - 邮箱: $INIT_ADMIN_EMAIL"
echo " - 密码: $INIT_ADMIN_PASSWORD"
echo ""
echo "MinIO控制台: http://$SERVER_IP:$MINIO_CONSOLE_PORT"
echo " - 用户名: minioadmin"
echo " - 密码: $MINIO_ROOT_PASSWORD"
echo ""
echo "数据库信息:"
echo " - 数据库名: flnxi"
echo " - 用户名: postgres"
echo " - 密码: $POSTGRES_PASSWORD"
echo "----------------------------------------"
echo ""
read -p "按回车键继续安装..."
else
echo -e "${GREEN}✓ 检测到已存在的配置文件${NC}"
if [ -f .env.production ]; then
source .env.production
elif [ -f .env ]; then
source .env
fi
# 检查并修复 DATABASE_URL(确保密码正确编码)
if [ -n "$POSTGRES_PASSWORD" ]; then
NEED_FIX_DATABASE_URL=0
# 检查 DATABASE_URL 是否存在
if [ -z "$DATABASE_URL" ]; then
echo -e "${YELLOW}检测到缺少 DATABASE_URL,正在生成...${NC}"
NEED_FIX_DATABASE_URL=1
# 检查 DATABASE_URL 中的密码是否未编码(包含特殊字符如 =、+、/)
elif echo "$DATABASE_URL" | grep -qE '[^%](=|/|\+)'; then
echo -e "${YELLOW}检测到 DATABASE_URL 中的密码可能未正确编码,正在修复...${NC}"
NEED_FIX_DATABASE_URL=1
fi
if [ "$NEED_FIX_DATABASE_URL" = "1" ]; then
# URL 编码密码
if command -v python3 &> /dev/null; then
POSTGRES_PASSWORD_ENCODED=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$POSTGRES_PASSWORD', safe=''))")
elif command -v python &> /dev/null; then
POSTGRES_PASSWORD_ENCODED=$(python -c "import urllib.parse; print(urllib.parse.quote('$POSTGRES_PASSWORD', safe=''))")
elif command -v perl &> /dev/null; then
POSTGRES_PASSWORD_ENCODED=$(perl -MURI::Escape -e "print uri_escape('$POSTGRES_PASSWORD');")
else
# 如果没有 Python 或 Perl,使用 sed 简单替换常见特殊字符
POSTGRES_PASSWORD_ENCODED=$(echo "$POSTGRES_PASSWORD" | sed 's/+/%2B/g; s/\//%2F/g; s/=/%3D/g; s/@/%40/g; s/:/%3A/g')
fi
POSTGRES_USER=${POSTGRES_USER:-postgres}
POSTGRES_DB=${POSTGRES_DB:-flnxi}
DATABASE_URL="postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD_ENCODED}@postgres:5432/${POSTGRES_DB}"
# 更新 .env.production 文件
if [ -f .env.production ]; then
if grep -q "^DATABASE_URL=" .env.production; then
# 如果已存在,替换
if [[ "$OSTYPE" == "darwin"* ]]; then
# macOS
sed -i '' "s|^DATABASE_URL=.*|DATABASE_URL=$DATABASE_URL|" .env.production
else
# Linux
sed -i "s|^DATABASE_URL=.*|DATABASE_URL=$DATABASE_URL|" .env.production
fi
echo -e "${GREEN}✓ 已更新 .env.production 中的 DATABASE_URL${NC}"
else
# 如果不存在,追加
echo "" >> .env.production
echo "# 完整的数据库连接URL(密码已URL编码,自动生成)" >> .env.production
echo "DATABASE_URL=$DATABASE_URL" >> .env.production
echo -e "${GREEN}✓ 已添加 DATABASE_URL 到 .env.production${NC}"
fi
fi
else
echo -e "${GREEN}✓ DATABASE_URL 配置正确${NC}"
fi
fi
# 确保 .env 文件与 .env.production 同步(供 docker compose 使用)
# 如果 .env.production 更新了,也要更新 .env
if [ -f .env.production ]; then
if [ ! -f .env ] || [ .env.production -nt .env ]; then
cp .env.production .env
echo -e "${GREEN}✓ 已同步 .env.production 到 .env(供 docker compose 使用)${NC}"
fi
fi
fi
# 创建必要的目录
echo ""
echo -e "${BLUE}[3/8]${NC} 创建数据目录..."
mkdir -p uploads
chmod 755 uploads
echo -e "${GREEN}✓ 数据目录创建完成${NC}"
# 登录 Harbor(如果配置了)
if [ -n "$HARBOR_USERNAME" ] && [ -n "$HARBOR_PASSWORD" ]; then
echo ""
echo -e "${BLUE}[4/8]${NC} 登录 Harbor 镜像仓库..."
echo "$HARBOR_PASSWORD" | docker login $HARBOR_URL -u $HARBOR_USERNAME --password-stdin
if [ $? -eq 0 ]; then
echo -e "${GREEN}✓ Harbor 登录成功${NC}"
else
echo -e "${YELLOW}警告: Harbor 登录失败,将尝试继续...${NC}"
fi
else
echo ""
echo -e "${BLUE}[4/8]${NC} 跳过 Harbor 登录(使用公开镜像或已登录)..."
fi
# 停止旧容器(如果存在)
echo ""
echo -e "${BLUE}[5/8]${NC} 清理旧容器..."
$DOCKER_COMPOSE -f docker-compose.prod.yml down 2>/dev/null || true
echo -e "${GREEN}✓ 清理完成${NC}"
# 拉取镜像(从 Harbor)
echo ""
echo -e "${BLUE}[6/8]${NC} 拉取 Docker 镜像..."
echo "这可能需要几分钟时间,请耐心等待..."
$DOCKER_COMPOSE -f docker-compose.prod.yml pull
echo -e "${GREEN}✓ 镜像拉取完成${NC}"
# 启动服务
echo ""
echo -e "${BLUE}[7/8]${NC} 启动服务..."
$DOCKER_COMPOSE -f docker-compose.prod.yml up -d
echo -e "${GREEN}✓ 服务启动成功${NC}"
# 等待服务就绪
echo ""
echo -e "${BLUE}[8/8]${NC} 等待服务启动..."
echo "正在等待数据库启动..."
sleep 10
# 检查服务状态
echo ""
echo "检查服务状态..."
$DOCKER_COMPOSE -f docker-compose.prod.yml ps
# 初始化数据库
echo ""
echo "初始化数据库..."
max_retries=30
retry_count=0
db_init_succeeded=0
while [ $retry_count -lt $max_retries ]; do
if $DOCKER_COMPOSE -f docker-compose.prod.yml exec -T app npx prisma db push --accept-data-loss 2>/dev/null; then
echo -e "${GREEN}✓ 数据库初始化成功${NC}"
db_init_succeeded=1
break
else
retry_count=$((retry_count + 1))
if [ $retry_count -lt $max_retries ]; then
echo "等待应用启动... ($retry_count/$max_retries)"
sleep 5
else
echo -e "${RED}数据库初始化失败,请稍后手动执行:${NC}"
echo " $DOCKER_COMPOSE -f docker-compose.prod.yml exec app npx prisma db push"
fi
fi
done
# 调用系统初始化接口,自动创建初始管理员账号等基础数据
if [ "$db_init_succeeded" -eq 1 ]; then
echo ""
echo "初始化系统管理员账号..."
# 如果缺少 APP_PORT / SERVER_IP,则使用保守默认值
INIT_SERVER_IP=${SERVER_IP:-127.0.0.1}
INIT_APP_PORT=${APP_PORT:-4320}
INIT_URL="http://${INIT_SERVER_IP}:${INIT_APP_PORT}/api/system/init"
if command -v curl >/dev/null 2>&1; then
max_init_retries=30
init_retry=0
while [ $init_retry -lt $max_init_retries ]; do
if curl -fsS -X POST "$INIT_URL" >/dev/null 2>&1; then
echo -e "${GREEN}✓ 系统初始化成功(管理员账号已创建或已存在)${NC}"
break
else
init_retry=$((init_retry + 1))
if [ $init_retry -lt $max_init_retries ]; then
echo "等待应用接口就绪... ($init_retry/$max_init_retries)"
sleep 5
else
echo -e "${RED}系统初始化接口调用失败,请稍后手动执行:${NC}"
echo " curl -X POST \"$INIT_URL\""
fi
fi
done
else
echo -e "${YELLOW}警告: 系统中未安装 curl,无法自动调用初始化接口${NC}"
echo "请在浏览器中访问以下地址完成初始化,或在服务器上手动执行 curl 命令:"
echo " $INIT_URL"
fi
fi
# 显示安装完成信息
echo ""
echo "========================================"
echo -e "${GREEN} 安装完成!${NC}"
echo "========================================"
echo ""
echo -e "${BLUE}访问地址:${NC}"
echo " 应用首页: http://$SERVER_IP:$APP_PORT"
echo " MinIO控制台: http://$SERVER_IP:$MINIO_CONSOLE_PORT"
echo ""
echo -e "${BLUE}MinIO 管理员账号:${NC}"
echo " 用户名: minioadmin"
echo " 密码: $MINIO_ROOT_PASSWORD"
echo ""
echo -e "${BLUE}首次使用:${NC}"
echo " 1. 访问 http://$SERVER_IP:$APP_PORT"
echo " 2. 使用初始管理员账号登录:"
echo " - 邮箱: $INIT_ADMIN_EMAIL"
echo " - 密码: $INIT_ADMIN_PASSWORD"
echo " 3. 登录后系统将强制要求修改密码(首次登录必须修改)"
echo ""
echo -e "${BLUE}常用命令:${NC}"
echo " 查看日志: $DOCKER_COMPOSE -f docker-compose.prod.yml logs -f"
echo " 重启服务: $DOCKER_COMPOSE -f docker-compose.prod.yml restart"
echo " 停止服务: $DOCKER_COMPOSE -f docker-compose.prod.yml down"
echo " 启动服务: $DOCKER_COMPOSE -f docker-compose.prod.yml up -d"
echo ""
echo -e "${YELLOW}注意事项:${NC}"
echo " - 所有配置信息已保存在 .env.production 文件中"
echo " - 请妥善保管初始管理员账号、数据库密码和MinIO密码"
echo " - 数据存储在 Docker volumes 中,卸载前请备份"
echo ""
echo "========================================"