收紧私有化部署配置与旧发布入口
This commit is contained in:
parent
c759d0a37b
commit
1f46544991
173
.dockerignore
173
.dockerignore
@ -1,52 +1,10 @@
|
|||||||
node_modules
|
|
||||||
.git
|
|
||||||
.gitignore
|
|
||||||
.env
|
|
||||||
.env.*
|
|
||||||
.DS_Store
|
|
||||||
npm-debug.log*
|
|
||||||
yarn-debug.log*
|
|
||||||
yarn-error.log*
|
|
||||||
pnpm-debug.log*
|
|
||||||
|
|
||||||
# Next.js build artifacts (we will copy from builder stage explicitly)
|
|
||||||
.next/cache
|
|
||||||
|
|
||||||
# Exclude all docs and scripts from production image
|
|
||||||
docs/**
|
|
||||||
scripts/**
|
|
||||||
# 但保留 docker-entrypoint.sh
|
|
||||||
!scripts/docker-entrypoint.sh
|
|
||||||
|
|
||||||
# Exclude local uploads and temp files
|
|
||||||
uploads/**
|
|
||||||
tmp/**
|
|
||||||
temp/**
|
|
||||||
|
|
||||||
# Exclude miscellaneous local files
|
|
||||||
*.log
|
|
||||||
*.md
|
|
||||||
!README.md
|
|
||||||
# 允许权威部署规范被查看
|
|
||||||
!docs/PORTABLE_DEPLOYMENT_GUIDE.md
|
|
||||||
*.bat
|
|
||||||
*.ps1
|
|
||||||
*.sh
|
|
||||||
|
|
||||||
# VCS/IDE
|
|
||||||
.vscode/**
|
|
||||||
.idea/**
|
|
||||||
*.swp
|
|
||||||
# ============================================
|
# ============================================
|
||||||
# Docker 构建排除文件配置
|
# Docker 构建排除文件配置
|
||||||
# 只包含功能性代码和必要文件
|
# 优化构建上下文大小,加速构建
|
||||||
# ============================================
|
# ============================================
|
||||||
|
|
||||||
# 依赖包(在 Docker 中重新安装)
|
# 依赖包(在 Docker 中重新安装)
|
||||||
node_modules
|
node_modules
|
||||||
npm-debug.log*
|
|
||||||
yarn-debug.log*
|
|
||||||
yarn-error.log*
|
|
||||||
.pnp
|
.pnp
|
||||||
.pnp.js
|
.pnp.js
|
||||||
|
|
||||||
@ -56,153 +14,88 @@ out
|
|||||||
build
|
build
|
||||||
dist
|
dist
|
||||||
|
|
||||||
# 测试
|
# 测试相关
|
||||||
coverage
|
coverage
|
||||||
*.test.ts
|
**/*.test.ts
|
||||||
*.test.tsx
|
**/*.test.tsx
|
||||||
*.spec.ts
|
**/*.spec.ts
|
||||||
*.spec.tsx
|
**/*.spec.tsx
|
||||||
test
|
|
||||||
tests
|
|
||||||
__tests__
|
__tests__
|
||||||
|
|
||||||
# 调试和日志
|
# 日志文件
|
||||||
*.log
|
*.log
|
||||||
logs
|
logs
|
||||||
*.log.*
|
|
||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
yarn-debug.log*
|
yarn-debug.log*
|
||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
|
|
||||||
# 本地环境变量(服务器上已有 .env)
|
# 本地环境变量(不要打包到镜像中,运行时通过 Docker 环境变量配置)
|
||||||
# 注意:.env.production 需要在构建时使用,所以不排除
|
|
||||||
.env.local
|
|
||||||
.env.development.local
|
|
||||||
.env.test.local
|
|
||||||
.env.production.local
|
|
||||||
.env
|
.env
|
||||||
|
.env.*
|
||||||
|
.env.local
|
||||||
|
.env.*.local
|
||||||
.env.example
|
.env.example
|
||||||
.env.template
|
.env.production
|
||||||
.env*.local
|
.env.production.local
|
||||||
# 但排除其他敏感文件
|
env-backups
|
||||||
server.env
|
config-bianliang.env
|
||||||
env-backups/
|
|
||||||
|
|
||||||
# 版本控制
|
# 版本控制
|
||||||
.git
|
.git
|
||||||
.gitignore
|
.gitignore
|
||||||
.gitattributes
|
.gitattributes
|
||||||
.gitmodules
|
|
||||||
|
|
||||||
# IDE 和编辑器配置
|
# IDE 配置
|
||||||
.vscode
|
.vscode
|
||||||
.idea
|
.idea
|
||||||
.cursor
|
.cursor
|
||||||
|
.trae
|
||||||
|
.kiro
|
||||||
*.swp
|
*.swp
|
||||||
*.swo
|
*.swo
|
||||||
*.sublime-project
|
|
||||||
*.sublime-workspace
|
|
||||||
.editorconfig
|
|
||||||
.eslintcache
|
|
||||||
|
|
||||||
# 文档文件(不部署文档)
|
# 文档(保留 VERSION.txt)
|
||||||
docs
|
docs
|
||||||
*.md
|
*.md
|
||||||
README.md
|
|
||||||
LICENSE
|
|
||||||
CHANGELOG.md
|
|
||||||
CONTRIBUTING.md
|
|
||||||
*.txt
|
|
||||||
!VERSION.txt
|
!VERSION.txt
|
||||||
|
|
||||||
# 脚本文件(不部署脚本)
|
# 脚本(保留 docker-entrypoint.sh)
|
||||||
scripts
|
scripts
|
||||||
!scripts/docker-entrypoint.sh
|
!scripts/docker-entrypoint.sh
|
||||||
*.sh
|
|
||||||
!scripts/docker-entrypoint.sh
|
|
||||||
*.ps1
|
|
||||||
*.bat
|
|
||||||
*.py
|
|
||||||
*.js
|
|
||||||
!next.config.js
|
|
||||||
!postcss.config.js
|
|
||||||
!tailwind.config.ts
|
|
||||||
|
|
||||||
# 临时文件和缓存
|
# 临时文件
|
||||||
uploads
|
uploads
|
||||||
public/uploads
|
public/uploads
|
||||||
backups
|
backups
|
||||||
*.tmp
|
*.tmp
|
||||||
*.temp
|
*.temp
|
||||||
.cache
|
.cache
|
||||||
.tmp
|
|
||||||
tmp
|
tmp
|
||||||
temp
|
temp
|
||||||
*.cache
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
|
|
||||||
# 压缩包和发布包
|
# 压缩包和部署产物
|
||||||
*.tar.gz
|
*.tar.gz
|
||||||
*.zip
|
*.zip
|
||||||
*.rar
|
|
||||||
*.7z
|
|
||||||
*.tar
|
*.tar
|
||||||
*.tgz
|
*.bak-*
|
||||||
release-*
|
*.ps1.bak-*
|
||||||
dist-*
|
|
||||||
deploy-artifacts
|
deploy-artifacts
|
||||||
|
docker-images
|
||||||
|
gongxiangp2-source-reference-20260309-202917
|
||||||
|
gongxiangp2-source-reference-20260413-172354
|
||||||
|
gongxiangp2-source-reference-old-style-20260513-132942
|
||||||
|
license-server
|
||||||
|
flnxi-agent
|
||||||
|
flnxi-agent-go
|
||||||
|
|
||||||
# 数据库备份文件
|
# 数据库文件
|
||||||
*.sql
|
*.sql
|
||||||
*.db
|
*.db
|
||||||
*.sqlite
|
*.sqlite
|
||||||
*.sqlite3
|
|
||||||
|
|
||||||
# 其他文件
|
# 其他
|
||||||
*.rpm
|
|
||||||
*.deb
|
|
||||||
*.pem
|
*.pem
|
||||||
*.key
|
*.key
|
||||||
*.cert
|
|
||||||
*.crt
|
|
||||||
frp_*
|
frp_*
|
||||||
docker-daemon-config.json
|
|
||||||
|
|
||||||
# 部署相关文件
|
|
||||||
deploy.sh
|
|
||||||
auto-deploy.sh
|
|
||||||
update-deploy.sh
|
|
||||||
deploy-to-server.sh
|
|
||||||
deploy-to-server.ps1
|
|
||||||
一键部署.bat
|
|
||||||
部署前检查清单.bat
|
|
||||||
安全部署.ps1
|
|
||||||
安全部署.sh
|
|
||||||
|
|
||||||
# 修复和工具脚本
|
|
||||||
修复PowerShell编码.ps1
|
|
||||||
快速修复编码.bat
|
|
||||||
*.修复*.bat
|
|
||||||
*.修复*.ps1
|
|
||||||
*.修复*.sh
|
|
||||||
|
|
||||||
# 配置文件(保留必要的,排除开发用的)
|
|
||||||
# 注意:以下文件会被包含(功能性代码)
|
|
||||||
# - package.json
|
|
||||||
# - package-lock.json
|
|
||||||
# - Dockerfile
|
|
||||||
# - docker-compose.yml
|
|
||||||
# - docker-compose.prod.yml
|
|
||||||
# - next.config.js
|
|
||||||
# - tsconfig.json
|
|
||||||
# - tailwind.config.ts
|
|
||||||
# - postcss.config.js
|
|
||||||
# - prisma/schema.prisma
|
|
||||||
# - prisma/migrations/ (迁移文件是功能性的)
|
|
||||||
# - app/
|
|
||||||
# - components/
|
|
||||||
# - lib/
|
|
||||||
# - types/
|
|
||||||
# - middleware.ts
|
|
||||||
# - public/ (静态资源)
|
|
||||||
|
|||||||
28
.env.example
28
.env.example
@ -2,14 +2,39 @@
|
|||||||
NODE_ENV=development
|
NODE_ENV=development
|
||||||
|
|
||||||
# Public URLs
|
# Public URLs
|
||||||
|
SERVER_IP=192.168.x.x
|
||||||
APP_PUBLIC_URL=https://your-domain.example.com
|
APP_PUBLIC_URL=https://your-domain.example.com
|
||||||
NEXTAUTH_URL=https://your-domain.example.com
|
NEXTAUTH_URL=https://your-domain.example.com
|
||||||
NEXTAUTH_SECRET=replace-with-strong-secret
|
NEXTAUTH_SECRET=replace-with-strong-secret
|
||||||
LICENSE_SERVER_URL=https://your-license-server.example.com
|
LICENSE_SERVER_URL=https://your-license-server.example.com
|
||||||
LICENSE_PROJECT_CODE=flnxi-sucai
|
LICENSE_PROJECT_CODE=flnxi-sucai
|
||||||
|
|
||||||
|
# Versioned deployment images
|
||||||
|
# Customer delivery must pin the full application image tag. Do not rely on latest.
|
||||||
|
APP_IMAGE=registry.example.com/flnxi/material/flnxi-material:1.0.0
|
||||||
|
# Optional: pin runtime images to your private Registry for offline or controlled delivery.
|
||||||
|
POSTGRES_IMAGE=postgres:15-alpine
|
||||||
|
MINIO_IMAGE=minio/minio:RELEASE.2025-04-22T22-12-26Z
|
||||||
|
MINIO_MC_IMAGE=minio/mc:RELEASE.2025-04-16T18-13-26Z
|
||||||
|
|
||||||
|
# Shared-machine isolation (recommended on test servers with multiple projects)
|
||||||
|
COMPOSE_PROJECT_NAME=flnxi_sucai
|
||||||
|
APP_CONTAINER_NAME=flnxi-material
|
||||||
|
POSTGRES_CONTAINER_NAME=flnxi-postgres
|
||||||
|
MINIO_CONTAINER_NAME=flnxi-minio
|
||||||
|
MINIO_INIT_CONTAINER_NAME=flnxi-minio-init
|
||||||
|
POSTGRES_VOLUME_NAME=flnxi-postgres-data
|
||||||
|
MINIO_VOLUME_NAME=flnxi-minio-data
|
||||||
|
APP_NETWORK_NAME=flnxi-app-network
|
||||||
|
APP_PORT=4320
|
||||||
|
MINIO_API_PORT=9000
|
||||||
|
MINIO_CONSOLE_PORT=9001
|
||||||
|
|
||||||
# Database settings
|
# Database settings
|
||||||
DATABASE_URL=postgresql://username:password@host:5432/database
|
DATABASE_URL=postgresql://username:password@host:5432/database
|
||||||
|
POSTGRES_USER=postgres
|
||||||
|
POSTGRES_PASSWORD=replace-with-strong-postgres-password
|
||||||
|
POSTGRES_DB=flnxi
|
||||||
|
|
||||||
# Object storage (S3 compatible)
|
# Object storage (S3 compatible)
|
||||||
STORAGE_PROVIDER=s3
|
STORAGE_PROVIDER=s3
|
||||||
@ -19,8 +44,11 @@ S3_ACCESS_KEY_ID=your-access-key
|
|||||||
S3_SECRET_ACCESS_KEY=your-secret-key
|
S3_SECRET_ACCESS_KEY=your-secret-key
|
||||||
S3_REGION=us-east-1
|
S3_REGION=us-east-1
|
||||||
S3_PUBLIC_DOMAIN=https://s3.example.com/your-bucket
|
S3_PUBLIC_DOMAIN=https://s3.example.com/your-bucket
|
||||||
|
S3_SET_ACL=0
|
||||||
S3_FILE_PATH=materials/
|
S3_FILE_PATH=materials/
|
||||||
S3_ENV_PREFIX=
|
S3_ENV_PREFIX=
|
||||||
|
MINIO_ROOT_USER=replace-with-minio-admin-user
|
||||||
|
MINIO_ROOT_PASSWORD=replace-with-strong-minio-admin-password
|
||||||
|
|
||||||
# Vector database (optional)
|
# Vector database (optional)
|
||||||
VECTOR_DB=pgvector
|
VECTOR_DB=pgvector
|
||||||
|
|||||||
@ -1,20 +0,0 @@
|
|||||||
DATABASE_URL=postgresql://gongxiangpan:ytPWwGYRptMmKezb@45.249.208.118:5432/gongxiangpan
|
|
||||||
DATABASE_DRIVER=node
|
|
||||||
|
|
||||||
# NextAuth
|
|
||||||
NEXTAUTH_SECRET=HzJhmmDRx8OS+ipZE/ersdsod/vk1FyoaLIhQSHygFk=
|
|
||||||
NEXTAUTH_URL=http://45.249.208.118:4320
|
|
||||||
APP_PUBLIC_URL=http://45.249.208.118:4320
|
|
||||||
|
|
||||||
# MinIO S3 Storage
|
|
||||||
S3_ENDPOINT=https://s3.1080p.hk
|
|
||||||
S3_BUCKET=jsxiangche
|
|
||||||
S3_ACCESS_KEY_ID=AEvT1p0wSNjow3Asu9NV
|
|
||||||
S3_SECRET_ACCESS_KEY=u8nzFl1S6dNoT5PQaQhkHz8Du6iX0hVnPswtvHhs
|
|
||||||
S3_REGION=us-east-1
|
|
||||||
S3_PUBLIC_DOMAIN=https://s3.1080p.hk/jsxiangche
|
|
||||||
S3_ENABLE_PATH_STYLE=1
|
|
||||||
S3_FILE_PATH=由壹点AI/
|
|
||||||
|
|
||||||
# Local storage for zip files
|
|
||||||
UPLOAD_DIR=./uploads
|
|
||||||
@ -158,7 +158,7 @@ RUN if [ ! -d "node_modules/.prisma" ]; then \
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# 构建 Next.js 应用(standalone 模式)
|
# 构建 Next.js 应用(standalone 模式)
|
||||||
# 允许构建时出现预渲染错误(这些页面会在运行时动态渲染)
|
# 商业交付镜像必须在构建阶段暴露失败,不能吞掉构建错误继续出包。
|
||||||
RUN set -ex && \
|
RUN set -ex && \
|
||||||
if [ -f "${PRISMA_ENGINE_CACHE_PATH}" ]; then \
|
if [ -f "${PRISMA_ENGINE_CACHE_PATH}" ]; then \
|
||||||
export PRISMA_QUERY_ENGINE_LIBRARY="${PRISMA_ENGINE_CACHE_PATH}" && \
|
export PRISMA_QUERY_ENGINE_LIBRARY="${PRISMA_ENGINE_CACHE_PATH}" && \
|
||||||
@ -172,7 +172,7 @@ RUN if [ ! -d "node_modules/.prisma" ]; then \
|
|||||||
else \
|
else \
|
||||||
unset PRISMA_SCHEMA_ENGINE_BINARY; \
|
unset PRISMA_SCHEMA_ENGINE_BINARY; \
|
||||||
fi && \
|
fi && \
|
||||||
npm run build || (echo "构建完成,但部分页面预渲染失败(这是预期的,这些页面将在运行时动态渲染)" && true)
|
npm run build
|
||||||
|
|
||||||
# 验证构建产物(允许 ESLint 警告,但不允许致命错误)
|
# 验证构建产物(允许 ESLint 警告,但不允许致命错误)
|
||||||
RUN if [ ! -f ".next/standalone/server.js" ]; then \
|
RUN if [ ! -f ".next/standalone/server.js" ]; then \
|
||||||
|
|||||||
22
__tests__/deployment-compose.test.ts
Normal file
22
__tests__/deployment-compose.test.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import fs from 'fs'
|
||||||
|
import path from 'path'
|
||||||
|
|
||||||
|
describe('docker-compose.prod.yml', () => {
|
||||||
|
const compose = fs.readFileSync(
|
||||||
|
path.join(process.cwd(), 'docker-compose.prod.yml'),
|
||||||
|
'utf8'
|
||||||
|
)
|
||||||
|
|
||||||
|
it('MinIO 初始化默认关闭素材桶匿名访问', () => {
|
||||||
|
expect(compose).toContain('S3_SET_ACL: ${S3_SET_ACL:-0}')
|
||||||
|
expect(compose).toContain('S3_PUBLIC_READ: ${S3_PUBLIC_READ:-0}')
|
||||||
|
expect(compose).toContain('mc anonymous set none "myminio/$${S3_BUCKET}"')
|
||||||
|
expect(compose).toContain('MinIO bucket anonymous access disabled')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('只有显式兼容公开模式时才打开匿名下载', () => {
|
||||||
|
expect(compose).toContain('WARNING: S3 public read compatibility is enabled')
|
||||||
|
expect(compose).toContain('mc anonymous set download "myminio/$${S3_BUCKET}"')
|
||||||
|
expect(compose).toContain('- S3_SET_ACL=${S3_SET_ACL:-0}')
|
||||||
|
})
|
||||||
|
})
|
||||||
@ -61,7 +61,7 @@ NEXT_PUBLIC_DINGTALK_CALLBACK_URL=
|
|||||||
# 图片远程域名白名单(Next/Image)
|
# 图片远程域名白名单(Next/Image)
|
||||||
# -----------------------------
|
# -----------------------------
|
||||||
# 用于 next.config.js 中的 remotePatterns。逗号分隔主机名;
|
# 用于 next.config.js 中的 remotePatterns。逗号分隔主机名;
|
||||||
# 默认已包含 s3.1080p.hk,如使用自有域名请在此追加。
|
# 如使用自有对象存储或 CDN 域名,请在此追加。
|
||||||
# 例如:cdn.company.com,images.company.com
|
# 例如:cdn.company.com,images.company.com
|
||||||
IMAGE_REMOTE_HOSTS=
|
IMAGE_REMOTE_HOSTS=
|
||||||
|
|
||||||
|
|||||||
@ -1,23 +1,23 @@
|
|||||||
# ================================================================
|
# ================================================================
|
||||||
# FLNXI素材库 - 部署脚本 v1.2
|
# FLNXI素材库 - 历史内部部署脚本 v1.2
|
||||||
# 功能:本地构建 -> 推送到Registry -> 远程部署
|
# 状态:已废弃,不作为客户安装、升级或正式发布入口
|
||||||
# 特性:自动加载基础镜像、智能重试、镜像清理、趣味提示
|
# 当前正式发布入口请使用 deploy-registry.ps1
|
||||||
# ================================================================
|
# ================================================================
|
||||||
|
|
||||||
param(
|
param(
|
||||||
[string]$Server = "45.249.208.118",
|
[string]$Server = "",
|
||||||
[string]$Username = "root",
|
[string]$Username = "root",
|
||||||
[SecureString]$Password = $null,
|
[SecureString]$Password = $null,
|
||||||
[string]$KeyPath = "",
|
[string]$KeyPath = "",
|
||||||
[string]$ImageName = "flnxi-material",
|
[string]$ImageName = "flnxi-material",
|
||||||
[string]$Tag = "pro-latest",
|
[string]$Tag = "",
|
||||||
[string]$RemotePath = "/root/deploy",
|
[string]$RemotePath = "/root/deploy",
|
||||||
[string]$EnvFile = "",
|
[string]$EnvFile = "",
|
||||||
[string]$Registry = "harbor", # 默认使用 Harbor
|
[string]$Registry = "harbor", # 默认使用 Harbor
|
||||||
[string]$HarborUrl = "harbor.flnxi.com",
|
[string]$HarborUrl = "",
|
||||||
[string]$HarborProjectName = "flnxi", # Harbor 项目名称,例如: flnxi, sucai, demo, library
|
[string]$HarborProjectName = "flnxi", # Harbor 项目名称,例如: flnxi, sucai, demo, library
|
||||||
[string]$HarborUsername = "admin",
|
[string]$HarborUsername = "admin",
|
||||||
[string]$HarborPassword = "Harbor12345",
|
[string]$HarborPassword = "",
|
||||||
[switch]$SkipCheck,
|
[switch]$SkipCheck,
|
||||||
[switch]$SkipBuild,
|
[switch]$SkipBuild,
|
||||||
[switch]$SkipUpload,
|
[switch]$SkipUpload,
|
||||||
@ -26,7 +26,8 @@ param(
|
|||||||
[switch]$CleanImages,
|
[switch]$CleanImages,
|
||||||
[int]$KeepImageCount = 5,
|
[int]$KeepImageCount = 5,
|
||||||
[switch]$CleanDockerDesktop = $true, # 默认启用清理 Docker Desktop 未使用资源
|
[switch]$CleanDockerDesktop = $true, # 默认启用清理 Docker Desktop 未使用资源
|
||||||
[switch]$NoCache # 禁用 Docker 构建缓存(默认使用缓存加速构建)
|
[switch]$NoCache, # 禁用 Docker 构建缓存(默认使用缓存加速构建)
|
||||||
|
[switch]$AcknowledgeLegacyInternalDeploy
|
||||||
)
|
)
|
||||||
|
|
||||||
# 脚本配置
|
# 脚本配置
|
||||||
@ -41,6 +42,13 @@ try {
|
|||||||
# 控制台编码设置失败时忽略
|
# 控制台编码设置失败时忽略
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (-not $Help -and -not $AcknowledgeLegacyInternalDeploy) {
|
||||||
|
Write-Host "[ERROR] deploy-new-fixed.ps1 是历史内部部署脚本,默认禁止执行。" -ForegroundColor Red
|
||||||
|
Write-Host "当前客户/测试发布请使用 deploy-registry.ps1。" -ForegroundColor Yellow
|
||||||
|
Write-Host "如确需排查历史链路,请补充 -AcknowledgeLegacyInternalDeploy,并显式传入 -Server、-Tag、-HarborUrl 和 Registry 凭据。" -ForegroundColor Yellow
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
$script:StageTracker = @{
|
$script:StageTracker = @{
|
||||||
Total = 0
|
Total = 0
|
||||||
Current = 0
|
Current = 0
|
||||||
@ -173,21 +181,24 @@ if ($Help) {
|
|||||||
Write-Host $Message -ForegroundColor $ForegroundColor
|
Write-Host $Message -ForegroundColor $ForegroundColor
|
||||||
}
|
}
|
||||||
|
|
||||||
Write-ColorOutput "新建简化部署脚本" "Green"
|
Write-ColorOutput "历史内部部署脚本(已废弃)" "Yellow"
|
||||||
Write-ColorOutput "========================================" "Green"
|
Write-ColorOutput "========================================" "Green"
|
||||||
|
Write-ColorOutput "此脚本不作为客户安装、升级或正式发布入口;当前请使用 deploy-registry.ps1。" "Yellow"
|
||||||
|
Write-ColorOutput "默认禁止执行,确需排查历史链路时必须追加 -AcknowledgeLegacyInternalDeploy。" "Yellow"
|
||||||
|
Write-ColorOutput "" "White"
|
||||||
Write-ColorOutput "用法:" "White"
|
Write-ColorOutput "用法:" "White"
|
||||||
Write-ColorOutput " .\deploy-new-fixed.ps1 [参数]" "Yellow"
|
Write-ColorOutput " .\deploy-new-fixed.ps1 -AcknowledgeLegacyInternalDeploy [参数]" "Yellow"
|
||||||
Write-ColorOutput "" "White"
|
Write-ColorOutput "" "White"
|
||||||
Write-ColorOutput "参数:" "White"
|
Write-ColorOutput "参数:" "White"
|
||||||
Write-ColorOutput " -Server [地址] 服务器地址 (默认: 45.249.208.118)" "Gray"
|
Write-ColorOutput " -Server [地址] 服务器地址,必须显式传入" "Gray"
|
||||||
Write-ColorOutput " -Username [用户] SSH用户名 (默认: root)" "Gray"
|
Write-ColorOutput " -Username [用户] SSH用户名 (默认: root)" "Gray"
|
||||||
Write-ColorOutput " -KeyPath [路径] SSH私钥路径 (推荐)" "Gray"
|
Write-ColorOutput " -KeyPath [路径] SSH私钥路径 (推荐)" "Gray"
|
||||||
Write-ColorOutput " -EnvFile [路径] 指定构建期使用的环境变量文件" "Gray"
|
Write-ColorOutput " -EnvFile [路径] 指定构建期使用的环境变量文件" "Gray"
|
||||||
Write-ColorOutput " -Registry [类型] Registry类型: default 或 harbor (默认: default)" "Gray"
|
Write-ColorOutput " -Registry [类型] Registry类型: default 或 harbor (默认: default)" "Gray"
|
||||||
Write-ColorOutput " -HarborUrl [地址] Harbor地址 (默认: harbor.flnxi.com)" "Gray"
|
Write-ColorOutput " -HarborUrl [地址] Harbor地址,必须显式传入" "Gray"
|
||||||
Write-ColorOutput " -HarborProjectName [名称] Harbor项目名称 (默认: flnxi)" "Gray"
|
Write-ColorOutput " -HarborProjectName [名称] Harbor项目名称 (默认: flnxi)" "Gray"
|
||||||
Write-ColorOutput " -HarborUsername [用户] Harbor用户名 (默认: admin)" "Gray"
|
Write-ColorOutput " -HarborUsername [用户] Harbor用户名 (默认: admin)" "Gray"
|
||||||
Write-ColorOutput " -HarborPassword [密码] Harbor密码 (默认: Harbor12345)" "Gray"
|
Write-ColorOutput " -HarborPassword [密码] Harbor密码,必须显式传入" "Gray"
|
||||||
Write-ColorOutput " -SkipCheck 跳过代码检查" "Gray"
|
Write-ColorOutput " -SkipCheck 跳过代码检查" "Gray"
|
||||||
Write-ColorOutput " -SkipBuild 跳过Docker构建" "Gray"
|
Write-ColorOutput " -SkipBuild 跳过Docker构建" "Gray"
|
||||||
Write-ColorOutput " -SkipUpload 跳过镜像推送和远程部署" "Gray"
|
Write-ColorOutput " -SkipUpload 跳过镜像推送和远程部署" "Gray"
|
||||||
@ -199,11 +210,7 @@ if ($Help) {
|
|||||||
Write-ColorOutput " -Help 显示此帮助信息" "Gray"
|
Write-ColorOutput " -Help 显示此帮助信息" "Gray"
|
||||||
Write-ColorOutput "" "White"
|
Write-ColorOutput "" "White"
|
||||||
Write-ColorOutput "示例:" "White"
|
Write-ColorOutput "示例:" "White"
|
||||||
Write-ColorOutput " .\deploy-new-fixed.ps1" "Yellow"
|
Write-ColorOutput " .\deploy-new-fixed.ps1 -AcknowledgeLegacyInternalDeploy -Server 192.168.1.20 -Tag 1.0.1 -HarborUrl registry.example.com" "Yellow"
|
||||||
Write-ColorOutput " .\deploy-new-fixed.ps1 -KeyPath 'C:\Users\YourUsername\.ssh\youyidian_prod_ed25519'" "Yellow"
|
|
||||||
Write-ColorOutput " .\deploy-new-fixed.ps1 -Registry harbor" "Yellow"
|
|
||||||
Write-ColorOutput " .\deploy-new-fixed.ps1 -SkipCheck -SkipUpload" "Yellow"
|
|
||||||
Write-ColorOutput " .\deploy-new-fixed.ps1 -CleanImages -KeepImageCount 5" "Yellow"
|
|
||||||
exit 0
|
exit 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -322,14 +329,14 @@ APP_PUBLIC_URL=http://$Server:4320
|
|||||||
NEXTAUTH_SECRET=$(New-Guid)
|
NEXTAUTH_SECRET=$(New-Guid)
|
||||||
|
|
||||||
# 存储配置
|
# 存储配置
|
||||||
S3_ENDPOINT=https://s3.1080p.hk
|
S3_ENDPOINT=https://s3.example.com
|
||||||
S3_BUCKET=jsxiangche
|
S3_BUCKET=placeholder-bucket
|
||||||
S3_ACCESS_KEY_ID=AEvT1p0wSNjow3Asu9NV
|
S3_ACCESS_KEY_ID=placeholder-access-key
|
||||||
S3_SECRET_ACCESS_KEY=u8nzFl1S6dNoT5PQaQhkHz8Du6iX0hVnPswtvHhs
|
S3_SECRET_ACCESS_KEY=placeholder-secret-key
|
||||||
S3_REGION=us-east-1
|
S3_REGION=us-east-1
|
||||||
S3_PUBLIC_DOMAIN=https://s3.1080p.hk/jsxiangche
|
S3_PUBLIC_DOMAIN=https://s3.example.com/placeholder-bucket
|
||||||
S3_ENABLE_PATH_STYLE=1
|
S3_ENABLE_PATH_STYLE=1
|
||||||
S3_FILE_PATH=由壹点AI/
|
S3_FILE_PATH=materials/
|
||||||
|
|
||||||
# 本地上传目录
|
# 本地上传目录
|
||||||
UPLOAD_DIR=./uploads
|
UPLOAD_DIR=./uploads
|
||||||
@ -414,6 +421,91 @@ function Get-TempFileName {
|
|||||||
return "${Prefix}_${timestamp}_${processId}_${random}.txt"
|
return "${Prefix}_${timestamp}_${processId}_${random}.txt"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ConvertTo-PlainText {
|
||||||
|
param([SecureString]$SecureValue)
|
||||||
|
if (-not $SecureValue) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
$credential = New-Object System.Management.Automation.PSCredential("placeholder", $SecureValue)
|
||||||
|
return $credential.GetNetworkCredential().Password
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-PlinkPath {
|
||||||
|
try {
|
||||||
|
$cmd = Get-Command "plink.exe" -ErrorAction Stop
|
||||||
|
return $cmd.Source
|
||||||
|
} catch {
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Start-RemoteProcess {
|
||||||
|
param(
|
||||||
|
[string]$Server,
|
||||||
|
[string]$Username,
|
||||||
|
[SecureString]$Password = $null,
|
||||||
|
[string]$KeyPath = "",
|
||||||
|
[string]$Command,
|
||||||
|
[string]$StdoutFile = "",
|
||||||
|
[string]$StderrFile = "",
|
||||||
|
[int]$ConnectTimeoutSeconds = 30
|
||||||
|
)
|
||||||
|
|
||||||
|
$target = "${Username}@${Server}"
|
||||||
|
$startParams = @{
|
||||||
|
NoNewWindow = $true
|
||||||
|
PassThru = $true
|
||||||
|
Wait = $true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not [string]::IsNullOrWhiteSpace($StdoutFile)) {
|
||||||
|
$startParams.RedirectStandardOutput = $StdoutFile
|
||||||
|
}
|
||||||
|
if (-not [string]::IsNullOrWhiteSpace($StderrFile)) {
|
||||||
|
$startParams.RedirectStandardError = $StderrFile
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($KeyPath -and (Test-Path $KeyPath)) {
|
||||||
|
$sshArgs = @(
|
||||||
|
"-o", "BatchMode=yes",
|
||||||
|
"-o", "StrictHostKeyChecking=no",
|
||||||
|
"-o", "ConnectTimeout=$ConnectTimeoutSeconds",
|
||||||
|
"-o", "ServerAliveInterval=10",
|
||||||
|
"-o", "ServerAliveCountMax=3",
|
||||||
|
"-i", $KeyPath,
|
||||||
|
$target,
|
||||||
|
$Command
|
||||||
|
)
|
||||||
|
return Start-Process -FilePath "ssh" -ArgumentList $sshArgs @startParams
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($Password) {
|
||||||
|
$plinkPath = Get-PlinkPath
|
||||||
|
if (-not $plinkPath) {
|
||||||
|
throw "未找到 plink.exe,无法使用密码执行非交互 SSH。"
|
||||||
|
}
|
||||||
|
|
||||||
|
$plainPassword = ConvertTo-PlainText -SecureValue $Password
|
||||||
|
$plinkArgs = @(
|
||||||
|
"-ssh",
|
||||||
|
"-batch",
|
||||||
|
"-pw", $plainPassword,
|
||||||
|
$target,
|
||||||
|
$Command
|
||||||
|
)
|
||||||
|
return Start-Process -FilePath $plinkPath -ArgumentList $plinkArgs @startParams
|
||||||
|
}
|
||||||
|
|
||||||
|
$sshArgs = @(
|
||||||
|
"-o", "StrictHostKeyChecking=no",
|
||||||
|
"-o", "ConnectTimeout=$ConnectTimeoutSeconds",
|
||||||
|
$target,
|
||||||
|
$Command
|
||||||
|
)
|
||||||
|
return Start-Process -FilePath "ssh" -ArgumentList $sshArgs @startParams
|
||||||
|
}
|
||||||
|
|
||||||
# 测试SSH连接(带重试机制)
|
# 测试SSH连接(带重试机制)
|
||||||
function Test-SSHConnection {
|
function Test-SSHConnection {
|
||||||
param(
|
param(
|
||||||
@ -441,28 +533,29 @@ function Test-SSHConnection {
|
|||||||
$stderrFile = Get-TempFileName -Prefix "ssh_stderr"
|
$stderrFile = Get-TempFileName -Prefix "ssh_stderr"
|
||||||
|
|
||||||
try {
|
try {
|
||||||
# 使用 OpenSSH(增加超时时间到20秒)
|
|
||||||
$sshArgs = @("-o", "BatchMode=yes", "-o", "ConnectTimeout=20", "-o", "ServerAliveInterval=5", "-o", "ServerAliveCountMax=3")
|
|
||||||
|
|
||||||
if ($KeyPath -and (Test-Path $KeyPath)) {
|
if ($KeyPath -and (Test-Path $KeyPath)) {
|
||||||
$sshArgs += @("-i", $KeyPath)
|
|
||||||
if ($attempt -eq 1) {
|
if ($attempt -eq 1) {
|
||||||
Write-Info "使用SSH密钥: $KeyPath"
|
Write-Info "使用SSH密钥: $KeyPath"
|
||||||
}
|
}
|
||||||
} elseif ($Password) {
|
} elseif ($Password) {
|
||||||
# 注意:SecureString 密码认证需要额外处理,当前主要使用 SSH 密钥
|
|
||||||
$sshArgs += @("-o", "PasswordAuthentication=yes")
|
|
||||||
if ($attempt -eq 1) {
|
if ($attempt -eq 1) {
|
||||||
Write-Warning "密码认证支持有限,建议使用 SSH 密钥"
|
Write-Info "使用 PuTTY plink 进行密码认证"
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if ($attempt -eq 1) {
|
if ($attempt -eq 1) {
|
||||||
Write-Warning "未提供密钥,使用交互式认证"
|
Write-Warning "未提供密钥,使用交互式认证"
|
||||||
}
|
}
|
||||||
$sshArgs += @("-o", "StrictHostKeyChecking=no")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$process = Start-Process -FilePath "ssh" -ArgumentList ($sshArgs + @($target, "echo 'SSH连接测试成功'")) -NoNewWindow -PassThru -Wait -RedirectStandardOutput $stdoutFile -RedirectStandardError $stderrFile
|
$process = Start-RemoteProcess `
|
||||||
|
-Server $Server `
|
||||||
|
-Username $Username `
|
||||||
|
-Password $Password `
|
||||||
|
-KeyPath $KeyPath `
|
||||||
|
-Command "echo 'SSH连接测试成功'" `
|
||||||
|
-StdoutFile $stdoutFile `
|
||||||
|
-StderrFile $stderrFile `
|
||||||
|
-ConnectTimeoutSeconds 20
|
||||||
|
|
||||||
if ($process.ExitCode -eq 0) {
|
if ($process.ExitCode -eq 0) {
|
||||||
Write-Success "SSH连接测试成功"
|
Write-Success "SSH连接测试成功"
|
||||||
@ -543,6 +636,7 @@ function Clean-RemoteDockerImages {
|
|||||||
param(
|
param(
|
||||||
[string]$Server,
|
[string]$Server,
|
||||||
[string]$Username,
|
[string]$Username,
|
||||||
|
[SecureString]$Password = $null,
|
||||||
[string]$KeyPath,
|
[string]$KeyPath,
|
||||||
[string]$ImageName,
|
[string]$ImageName,
|
||||||
[int]$KeepCount = 3
|
[int]$KeepCount = 3
|
||||||
@ -562,14 +656,14 @@ echo '--- 清理后的镜像列表 ---'
|
|||||||
docker images | grep '$ImageName' | head -10
|
docker images | grep '$ImageName' | head -10
|
||||||
"@
|
"@
|
||||||
|
|
||||||
$sshArgs = @("-o", "BatchMode=yes", "-o", "StrictHostKeyChecking=no", "-o", "ConnectTimeout=30")
|
|
||||||
if ($KeyPath -and (Test-Path $KeyPath)) {
|
|
||||||
$sshArgs += @("-i", $KeyPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$sshArgs += @("${Username}@${Server}", $cleanCmd)
|
$process = Start-RemoteProcess `
|
||||||
$process = Start-Process -FilePath "ssh" -ArgumentList $sshArgs -NoNewWindow -PassThru -Wait
|
-Server $Server `
|
||||||
|
-Username $Username `
|
||||||
|
-Password $Password `
|
||||||
|
-KeyPath $KeyPath `
|
||||||
|
-Command $cleanCmd `
|
||||||
|
-ConnectTimeoutSeconds 30
|
||||||
if ($process.ExitCode -eq 0) {
|
if ($process.ExitCode -eq 0) {
|
||||||
Write-Success "服务器镜像清理完成"
|
Write-Success "服务器镜像清理完成"
|
||||||
return $true
|
return $true
|
||||||
@ -1017,27 +1111,31 @@ function Main {
|
|||||||
# 因为镜像已经在本地构建完成,所有层(包括基础镜像层)都已包含在镜像中
|
# 因为镜像已经在本地构建完成,所有层(包括基础镜像层)都已包含在镜像中
|
||||||
# 在服务器上拉取镜像并部署(带重试机制)
|
# 在服务器上拉取镜像并部署(带重试机制)
|
||||||
Write-Info "在服务器上拉取镜像并部署..."
|
Write-Info "在服务器上拉取镜像并部署..."
|
||||||
|
$composeCmd = "docker compose -f docker-compose.prod.yml"
|
||||||
# 如果使用 Harbor,需要在服务器上先登录
|
# 如果使用 Harbor,需要在服务器上先登录
|
||||||
if ($Registry -eq "harbor") {
|
if ($Registry -eq "harbor") {
|
||||||
$harborLoginCmd = "docker login $HarborUrl -u $HarborUsername -p $HarborPassword"
|
$harborLoginCmd = "docker login $HarborUrl -u $HarborUsername -p $HarborPassword"
|
||||||
$deployCmd = "cd $RemotePath && $harborLoginCmd && docker-compose stop app && docker-compose rm -f app && docker pull $registryImage && docker tag $registryImage $fullImage && docker-compose up -d --no-deps --pull never app"
|
$deployCmd = "cd $RemotePath && $harborLoginCmd && $composeCmd stop app && $composeCmd rm -f app && docker pull $registryImage && docker tag $registryImage $fullImage && $composeCmd up -d --no-deps --pull never app"
|
||||||
} else {
|
} else {
|
||||||
# 先停止并删除旧容器,然后拉取新镜像,重新启动(使用 --no-deps --pull never 避免拉取依赖服务和重复拉取)
|
# 先停止并删除旧容器,然后拉取新镜像,重新启动(使用 --no-deps --pull never 避免拉取依赖服务和重复拉取)
|
||||||
$deployCmd = "cd $RemotePath && docker-compose stop app && docker-compose rm -f app && docker pull $registryImage && docker tag $registryImage $fullImage && docker-compose up -d --no-deps --pull never app"
|
$deployCmd = "cd $RemotePath && $composeCmd stop app && $composeCmd rm -f app && docker pull $registryImage && docker tag $registryImage $fullImage && $composeCmd up -d --no-deps --pull never app"
|
||||||
}
|
}
|
||||||
$sshArgs = @("-o", "BatchMode=yes", "-o", "StrictHostKeyChecking=no", "-o", "ConnectTimeout=30", "-o", "ServerAliveInterval=10", "-o", "ServerAliveCountMax=3")
|
$remoteCommandAvailable = $false
|
||||||
if ($KeyPath -and (Test-Path $KeyPath)) {
|
if ($KeyPath -and (Test-Path $KeyPath)) {
|
||||||
$sshArgs += @("-i", $KeyPath)
|
|
||||||
Write-Info "使用 OpenSSH 执行远程部署命令"
|
Write-Info "使用 OpenSSH 执行远程部署命令"
|
||||||
|
$remoteCommandAvailable = $true
|
||||||
} elseif ($Password) {
|
} elseif ($Password) {
|
||||||
Write-Warning "未提供 SSH 密钥,无法在非交互模式下使用密码。请手动执行部署命令。"
|
if (Get-PlinkPath) {
|
||||||
$sshArgs = $null
|
Write-Info "使用 PuTTY plink 执行远程部署命令"
|
||||||
|
$remoteCommandAvailable = $true
|
||||||
|
} else {
|
||||||
|
Write-Warning "本机未找到 plink.exe,无法使用密码自动部署。请手动执行部署命令。"
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Write-Warning "缺少 SSH 凭据,请手动执行部署命令。"
|
Write-Warning "缺少 SSH 凭据,请手动执行部署命令。"
|
||||||
$sshArgs = $null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($sshArgs) {
|
if ($remoteCommandAvailable) {
|
||||||
$deployMaxRetries = 2
|
$deployMaxRetries = 2
|
||||||
$deployRetryDelay = 5
|
$deployRetryDelay = 5
|
||||||
$deployAttempt = 0
|
$deployAttempt = 0
|
||||||
@ -1050,9 +1148,14 @@ function Main {
|
|||||||
Start-Sleep -Seconds $deployRetryDelay
|
Start-Sleep -Seconds $deployRetryDelay
|
||||||
}
|
}
|
||||||
|
|
||||||
$currentSshArgs = $sshArgs + @("${Username}@${Server}", $deployCmd)
|
|
||||||
try {
|
try {
|
||||||
$sshProcess = Start-Process -FilePath "ssh" -ArgumentList $currentSshArgs -NoNewWindow -PassThru -Wait
|
$sshProcess = Start-RemoteProcess `
|
||||||
|
-Server $Server `
|
||||||
|
-Username $Username `
|
||||||
|
-Password $Password `
|
||||||
|
-KeyPath $KeyPath `
|
||||||
|
-Command $deployCmd `
|
||||||
|
-ConnectTimeoutSeconds 30
|
||||||
if ($sshProcess.ExitCode -eq 0) {
|
if ($sshProcess.ExitCode -eq 0) {
|
||||||
Write-Success "部署成功"
|
Write-Success "部署成功"
|
||||||
$deploySuccess = $true
|
$deploySuccess = $true
|
||||||
@ -1076,7 +1179,7 @@ function Main {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (-not $sshTest -or -not $sshArgs) {
|
if (-not $sshTest -or -not $remoteCommandAvailable) {
|
||||||
Write-Warning "远程部署阶段未完成,已提供手动命令指引"
|
Write-Warning "远程部署阶段未完成,已提供手动命令指引"
|
||||||
} elseif ($sshTest) {
|
} elseif ($sshTest) {
|
||||||
Write-Info "开始对线上应用做一次健康检查..."
|
Write-Info "开始对线上应用做一次健康检查..."
|
||||||
@ -1095,8 +1198,8 @@ function Main {
|
|||||||
Clean-LocalDockerImages -ImageName $ImageName -KeepTag $Tag -KeepCount $KeepImageCount
|
Clean-LocalDockerImages -ImageName $ImageName -KeepTag $Tag -KeepCount $KeepImageCount
|
||||||
|
|
||||||
# 清理服务器镜像
|
# 清理服务器镜像
|
||||||
if ($sshTest -and $sshArgs) {
|
if ($sshTest -and $remoteCommandAvailable) {
|
||||||
Clean-RemoteDockerImages -Server $Server -Username $Username -KeyPath $KeyPath -ImageName $ImageName -KeepCount $KeepImageCount
|
Clean-RemoteDockerImages -Server $Server -Username $Username -Password $Password -KeyPath $KeyPath -ImageName $ImageName -KeepCount $KeepImageCount
|
||||||
}
|
}
|
||||||
|
|
||||||
# 清理 Registry 镜像(仅适用于旧 Registry)
|
# 清理 Registry 镜像(仅适用于旧 Registry)
|
||||||
@ -1136,18 +1239,18 @@ function Main {
|
|||||||
Write-Info "如果自动部署失败,请在服务器上手动执行:"
|
Write-Info "如果自动部署失败,请在服务器上手动执行:"
|
||||||
Write-Info " cd $RemotePath"
|
Write-Info " cd $RemotePath"
|
||||||
Write-Info " docker login $HarborUrl -u $HarborUsername -p $HarborPassword"
|
Write-Info " docker login $HarborUrl -u $HarborUsername -p $HarborPassword"
|
||||||
Write-Info " docker-compose stop app && docker-compose rm -f app"
|
Write-Info " docker compose -f docker-compose.prod.yml stop app && docker compose -f docker-compose.prod.yml rm -f app"
|
||||||
Write-Info " docker pull $registryImage"
|
Write-Info " docker pull $registryImage"
|
||||||
Write-Info " docker tag $registryImage $fullImage"
|
Write-Info " docker tag $registryImage $fullImage"
|
||||||
Write-Info " docker-compose up -d --no-deps --pull never app"
|
Write-Info " docker compose -f docker-compose.prod.yml up -d --no-deps --pull never app"
|
||||||
} else {
|
} else {
|
||||||
Write-Info "镜像已推送到 Registry: $registryImage"
|
Write-Info "镜像已推送到 Registry: $registryImage"
|
||||||
Write-Info "如果自动部署失败,请在服务器上手动执行:"
|
Write-Info "如果自动部署失败,请在服务器上手动执行:"
|
||||||
Write-Info " cd $RemotePath"
|
Write-Info " cd $RemotePath"
|
||||||
Write-Info " docker-compose stop app && docker-compose rm -f app"
|
Write-Info " docker compose -f docker-compose.prod.yml stop app && docker compose -f docker-compose.prod.yml rm -f app"
|
||||||
Write-Info " docker pull $registryImage"
|
Write-Info " docker pull $registryImage"
|
||||||
Write-Info " docker tag $registryImage $fullImage"
|
Write-Info " docker tag $registryImage $fullImage"
|
||||||
Write-Info " docker-compose up -d --no-deps --pull never app"
|
Write-Info " docker compose -f docker-compose.prod.yml up -d --no-deps --pull never app"
|
||||||
}
|
}
|
||||||
Write-Info " # 如需执行数据库迁移,请手动运行: docker exec flnxi-material npx prisma migrate deploy"
|
Write-Info " # 如需执行数据库迁移,请手动运行: docker exec flnxi-material npx prisma migrate deploy"
|
||||||
}
|
}
|
||||||
@ -1167,4 +1270,3 @@ function Main {
|
|||||||
|
|
||||||
# 执行主函数
|
# 执行主函数
|
||||||
Main
|
Main
|
||||||
|
|
||||||
|
|||||||
@ -3,21 +3,22 @@
|
|||||||
# 支持内网穿透,资源存储在MinIO存储桶
|
# 支持内网穿透,资源存储在MinIO存储桶
|
||||||
# 从私有镜像仓库拉取镜像,无需源码
|
# 从私有镜像仓库拉取镜像,无需源码
|
||||||
# ============================================
|
# ============================================
|
||||||
# Harbor 项目结构:
|
# Registry 项目结构:
|
||||||
# - flnxi-public (公开): Agent、runtime 依赖镜像
|
# - flnxi/runtime:PostgreSQL、MinIO 等运行依赖镜像
|
||||||
# - flnxi (私有): 素材系统镜像,需 token 拉取
|
# - flnxi/material 或 flnxi/material-dev:素材系统正式版 / 测试版镜像
|
||||||
|
# 客户交付必须在 .env 中显式填写 APP_IMAGE,避免误拉旧仓库或 latest。
|
||||||
# ============================================
|
# ============================================
|
||||||
|
|
||||||
services:
|
services:
|
||||||
# PostgreSQL 数据库
|
# PostgreSQL 数据库
|
||||||
postgres:
|
postgres:
|
||||||
# 从 Harbor 的 flnxi-public 项目拉取(公开,无需登录)
|
# 可由 .env 覆盖为私有 Registry runtime 镜像;默认走 Docker Hub 官方镜像。
|
||||||
image: ${POSTGRES_IMAGE:-harbor.flnxi.com/flnxi-public/runtime/postgres:15-alpine}
|
image: ${POSTGRES_IMAGE:-postgres:15-alpine}
|
||||||
container_name: flnxi-postgres
|
container_name: ${POSTGRES_CONTAINER_NAME:-flnxi-postgres}
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
environment:
|
environment:
|
||||||
POSTGRES_USER: ${POSTGRES_USER:-postgres}
|
POSTGRES_USER: ${POSTGRES_USER:-postgres}
|
||||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-youyidian123456}
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD is required}
|
||||||
POSTGRES_DB: ${POSTGRES_DB:-flnxi}
|
POSTGRES_DB: ${POSTGRES_DB:-flnxi}
|
||||||
POSTGRES_INITDB_ARGS: "--encoding=UTF-8"
|
POSTGRES_INITDB_ARGS: "--encoding=UTF-8"
|
||||||
volumes:
|
volumes:
|
||||||
@ -33,14 +34,14 @@ services:
|
|||||||
# MinIO 对象存储
|
# MinIO 对象存储
|
||||||
# 使用特定版本号确保稳定性(符合项目宗旨:稳定性优先)
|
# 使用特定版本号确保稳定性(符合项目宗旨:稳定性优先)
|
||||||
minio:
|
minio:
|
||||||
# 从 Harbor 的 flnxi-public 项目拉取(公开,无需登录)
|
# 可由 .env 覆盖为私有 Registry runtime 镜像;默认走官方镜像。
|
||||||
image: ${MINIO_IMAGE:-harbor.flnxi.com/flnxi-public/runtime/minio:RELEASE.2025-04-22T22-12-26Z}
|
image: ${MINIO_IMAGE:-minio/minio:RELEASE.2025-04-22T22-12-26Z}
|
||||||
container_name: flnxi-minio
|
container_name: ${MINIO_CONTAINER_NAME:-flnxi-minio}
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
command: server /data --console-address ":9001"
|
command: server /data --console-address ":9001"
|
||||||
environment:
|
environment:
|
||||||
MINIO_ROOT_USER: ${MINIO_ROOT_USER:-minioadmin}
|
MINIO_ROOT_USER: ${MINIO_ROOT_USER:?MINIO_ROOT_USER is required}
|
||||||
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:-minioadmin123456}
|
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:?MINIO_ROOT_PASSWORD is required}
|
||||||
volumes:
|
volumes:
|
||||||
- minio_data:/data
|
- minio_data:/data
|
||||||
ports:
|
ports:
|
||||||
@ -55,32 +56,47 @@ services:
|
|||||||
retries: 3
|
retries: 3
|
||||||
|
|
||||||
# MinIO 初始化服务(创建bucket)
|
# MinIO 初始化服务(创建bucket)
|
||||||
# 使用 latest 标签确保可用性(mc 客户端版本兼容性好)
|
|
||||||
minio-init:
|
minio-init:
|
||||||
# 从 Harbor 的 flnxi-public 项目拉取(公开,无需登录)
|
# mc 客户端用于初始化桶;离线或客户现场可在 .env 中覆盖为私有 Registry 镜像。
|
||||||
image: ${MINIO_MC_IMAGE:-harbor.flnxi.com/flnxi-public/runtime/minio-mc:latest}
|
image: ${MINIO_MC_IMAGE:-minio/mc:RELEASE.2025-04-16T18-13-26Z}
|
||||||
container_name: flnxi-minio-init
|
container_name: ${MINIO_INIT_CONTAINER_NAME:-flnxi-minio-init}
|
||||||
|
environment:
|
||||||
|
MINIO_ROOT_USER: ${MINIO_ROOT_USER:?MINIO_ROOT_USER is required}
|
||||||
|
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:?MINIO_ROOT_PASSWORD is required}
|
||||||
|
S3_BUCKET: ${S3_BUCKET:-flnxi-materials}
|
||||||
|
S3_SET_ACL: ${S3_SET_ACL:-0}
|
||||||
|
S3_PUBLIC_READ: ${S3_PUBLIC_READ:-0}
|
||||||
depends_on:
|
depends_on:
|
||||||
minio:
|
minio:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
entrypoint: >
|
entrypoint: |
|
||||||
/bin/sh -c "
|
/bin/sh -c '
|
||||||
|
set -e;
|
||||||
sleep 5;
|
sleep 5;
|
||||||
mc alias set myminio http://minio:9000 ${MINIO_ROOT_USER:-minioadmin} ${MINIO_ROOT_PASSWORD:-minioadmin123456};
|
mc alias set myminio http://minio:9000 "$${MINIO_ROOT_USER}" "$${MINIO_ROOT_PASSWORD}";
|
||||||
mc mb myminio/${S3_BUCKET:-flnxi-materials} --ignore-existing;
|
mc mb "myminio/$${S3_BUCKET}" --ignore-existing;
|
||||||
mc anonymous set download myminio/${S3_BUCKET:-flnxi-materials};
|
PUBLIC_READ="$$(printf "%s" "$${S3_SET_ACL:-0}:$${S3_PUBLIC_READ:-0}" | tr "[:upper:]" "[:lower:]")";
|
||||||
echo 'MinIO bucket created and configured successfully';
|
case "$$PUBLIC_READ" in
|
||||||
exit 0;
|
1:*|true:*|yes:*|*:1|*:true|*:yes)
|
||||||
"
|
echo "WARNING: S3 public read compatibility is enabled; bucket anonymous download will be allowed";
|
||||||
|
mc anonymous set download "myminio/$${S3_BUCKET}";
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
mc anonymous set none "myminio/$${S3_BUCKET}";
|
||||||
|
echo "MinIO bucket anonymous access disabled";
|
||||||
|
;;
|
||||||
|
esac;
|
||||||
|
echo "MinIO bucket created and configured successfully";
|
||||||
|
'
|
||||||
networks:
|
networks:
|
||||||
- app-network
|
- app-network
|
||||||
|
|
||||||
# 主应用(Pro版 - 从私有镜像仓库拉取,需 token)
|
# 主应用(Pro版 - 从私有镜像仓库拉取,需 token)
|
||||||
app:
|
app:
|
||||||
# 从 Harbor 的 flnxi 项目拉取(私有,需许可证 token 登录)
|
# 客户交付必须显式填写完整 APP_IMAGE,例如:
|
||||||
# Agent 会在验证许可证后自动获取 token 并拉取此镜像
|
# APP_IMAGE=registry.example.com/flnxi/material/flnxi-material:1.0.1
|
||||||
image: ${APP_IMAGE:-harbor.flnxi.com/flnxi/flnxi-material:pro-${IMAGE_TAG:-latest}}
|
image: ${APP_IMAGE:?APP_IMAGE is required, for example registry.example.com/flnxi/material/flnxi-material:1.0.1}
|
||||||
container_name: flnxi-material
|
container_name: ${APP_CONTAINER_NAME:-flnxi-material}
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
- "${APP_PORT:-4320}:3000"
|
- "${APP_PORT:-4320}:3000"
|
||||||
@ -93,33 +109,34 @@ services:
|
|||||||
# - SERVER_IP 必须在安装脚本或 .env 中配置为「本机在局域网中的访问 IP」,例如:192.168.41.144
|
# - SERVER_IP 必须在安装脚本或 .env 中配置为「本机在局域网中的访问 IP」,例如:192.168.41.144
|
||||||
# - 对于一键安装用户,install.sh 会自动生成 .env.production 并写入 SERVER_IP,这里直接引用即可
|
# - 对于一键安装用户,install.sh 会自动生成 .env.production 并写入 SERVER_IP,这里直接引用即可
|
||||||
- NODE_ENV=production
|
- NODE_ENV=production
|
||||||
# DATABASE_URL 从 .env 文件自动读取(install.sh 会生成正确编码的 URL)
|
# DATABASE_URL 从 .env 文件自动读取;未配置时按同一份 .env 中的数据库变量拼接
|
||||||
# 如果 .env 文件中没有 DATABASE_URL,则使用默认值(向后兼容)
|
- DATABASE_URL=${DATABASE_URL:-postgresql://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB:-flnxi}}
|
||||||
- DATABASE_URL=${DATABASE_URL:-postgresql://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-youyidian123456}@postgres:5432/${POSTGRES_DB:-flnxi}}
|
|
||||||
- DATABASE_DRIVER=node
|
- DATABASE_DRIVER=node
|
||||||
- STORAGE_PROVIDER=s3
|
- STORAGE_PROVIDER=s3
|
||||||
- S3_ENDPOINT=http://minio:9000
|
- S3_ENDPOINT=http://minio:9000
|
||||||
- S3_BUCKET=${S3_BUCKET:-flnxi-materials}
|
- S3_BUCKET=${S3_BUCKET:-flnxi-materials}
|
||||||
- S3_ACCESS_KEY_ID=${MINIO_ROOT_USER:-minioadmin}
|
- S3_ACCESS_KEY_ID=${MINIO_ROOT_USER}
|
||||||
- S3_SECRET_ACCESS_KEY=${MINIO_ROOT_PASSWORD:-minioadmin123456}
|
- S3_SECRET_ACCESS_KEY=${MINIO_ROOT_PASSWORD}
|
||||||
- S3_REGION=us-east-1
|
- S3_REGION=us-east-1
|
||||||
# 对外访问 MinIO 的地址(供前端和分享链接使用),必须是局域网 / 公网可访问的 IP 或域名
|
# 对外访问 MinIO 的地址(供前端和分享链接使用),必须是局域网 / 公网可访问的 IP 或域名
|
||||||
# 这里不再默认 127.0.0.1,避免在 NAS 等环境下写死 localhost
|
# 这里不再默认 127.0.0.1,避免在 NAS 等环境下写死 localhost
|
||||||
- S3_PUBLIC_DOMAIN=http://${SERVER_IP}:${MINIO_API_PORT:-9000}/${S3_BUCKET:-flnxi-materials}
|
- S3_PUBLIC_DOMAIN=http://${SERVER_IP}:${MINIO_API_PORT:-9000}/${S3_BUCKET:-flnxi-materials}
|
||||||
- S3_ENABLE_PATH_STYLE=1
|
- S3_ENABLE_PATH_STYLE=1
|
||||||
- S3_SET_ACL=1
|
- S3_SET_ACL=${S3_SET_ACL:-0}
|
||||||
- S3_PREVIEW_URL_EXPIRE_IN=7200
|
- S3_PREVIEW_URL_EXPIRE_IN=7200
|
||||||
- S3_FILE_PATH=materials/
|
- S3_FILE_PATH=materials/
|
||||||
- NEXT_PUBLIC_S3_FILE_PATH=materials/
|
- NEXT_PUBLIC_S3_FILE_PATH=materials/
|
||||||
# NextAuth 的外部访问地址,从 .env(.production) 中注入(不在这里拼接,以免 SERVER_IP 为空时产生 http://:4320)
|
# NextAuth 的外部访问地址,从 .env(.production) 中注入(不在这里拼接,以免 SERVER_IP 为空时产生 http://:4320)
|
||||||
# install.sh / 部署脚本会写入 NEXTAUTH_URL,例如:http://192.168.41.144:4320
|
# install.sh / 部署脚本会写入 NEXTAUTH_URL,例如:http://192.168.41.144:4320
|
||||||
- NEXTAUTH_URL=${NEXTAUTH_URL}
|
- NEXTAUTH_URL=${NEXTAUTH_URL}
|
||||||
- NEXTAUTH_SECRET=${NEXTAUTH_SECRET:-change-this-to-a-random-string-in-production}
|
- NEXTAUTH_SECRET=${NEXTAUTH_SECRET:?NEXTAUTH_SECRET is required}
|
||||||
# 初始管理员账号(供 /api/system/init 使用)
|
# 初始管理员账号(供 /api/system/init 使用)
|
||||||
- INIT_ADMIN_EMAIL=${INIT_ADMIN_EMAIL:-admin@flnxi.com}
|
- INIT_ADMIN_EMAIL=${INIT_ADMIN_EMAIL:-admin@local.flnxi}
|
||||||
- INIT_ADMIN_PASSWORD=${INIT_ADMIN_PASSWORD:-88888888}
|
- INIT_ADMIN_PASSWORD=${INIT_ADMIN_PASSWORD:-}
|
||||||
|
- SYSTEM_INIT_TOKEN=${SYSTEM_INIT_TOKEN:-}
|
||||||
volumes:
|
volumes:
|
||||||
- ./uploads:/app/uploads
|
- ./uploads:/app/uploads
|
||||||
|
- ./data:/app/data
|
||||||
depends_on:
|
depends_on:
|
||||||
postgres:
|
postgres:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
@ -130,19 +147,22 @@ services:
|
|||||||
networks:
|
networks:
|
||||||
- app-network
|
- app-network
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3000/api/health"]
|
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:3000/api/health"]
|
||||||
interval: 30s
|
interval: 30s
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
retries: 3
|
retries: 3
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
postgres_data:
|
postgres_data:
|
||||||
|
name: ${POSTGRES_VOLUME_NAME:-flnxi-postgres-data}
|
||||||
driver: local
|
driver: local
|
||||||
minio_data:
|
minio_data:
|
||||||
|
name: ${MINIO_VOLUME_NAME:-flnxi-minio-data}
|
||||||
driver: local
|
driver: local
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
app-network:
|
app-network:
|
||||||
|
name: ${APP_NETWORK_NAME:-flnxi-app-network}
|
||||||
driver: bridge
|
driver: bridge
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
24
docker-compose.source-test.yml
Normal file
24
docker-compose.source-test.yml
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
image: ${POSTGRES_IMAGE:-postgres:16}
|
||||||
|
pull_policy: never
|
||||||
|
|
||||||
|
minio:
|
||||||
|
image: ${MINIO_IMAGE:-minio/minio:RELEASE.2025-04-22T22-12-26Z}
|
||||||
|
pull_policy: never
|
||||||
|
|
||||||
|
minio-init:
|
||||||
|
image: ${MINIO_MC_IMAGE:-minio/mc:RELEASE.2025-04-16T18-13-26Z}
|
||||||
|
pull_policy: never
|
||||||
|
|
||||||
|
app:
|
||||||
|
image: ${APP_IMAGE:-flnxi-sucai-test-local:${IMAGE_TAG:-latest}}
|
||||||
|
pull_policy: never
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
args:
|
||||||
|
DATABASE_URL: ${DATABASE_URL:-postgresql://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB:-flnxi}}
|
||||||
|
NEXTAUTH_URL: ${NEXTAUTH_URL}
|
||||||
|
APP_PUBLIC_URL: ${APP_PUBLIC_URL}
|
||||||
|
APP_VERSION: ${APP_VERSION:-source-test}
|
||||||
@ -28,7 +28,7 @@ services:
|
|||||||
# DATABASE_DRIVER=${DATABASE_DRIVER}
|
# DATABASE_DRIVER=${DATABASE_DRIVER}
|
||||||
# 站点公共URL(可选,优先用于生成邀请链接等)
|
# 站点公共URL(可选,优先用于生成邀请链接等)
|
||||||
# APP_PUBLIC_URL=${APP_PUBLIC_URL}
|
# APP_PUBLIC_URL=${APP_PUBLIC_URL}
|
||||||
- KEY_VAULTS_SECRET=/7DcksaICAECGjB38O3s8AE0wj2NoGzwIog0iUUMugY=
|
- KEY_VAULTS_SECRET=${KEY_VAULTS_SECRET:-}
|
||||||
- VECTOR_DB=pgvector
|
- VECTOR_DB=pgvector
|
||||||
- PGVECTOR_DB_URL=${DATABASE_URL}
|
- PGVECTOR_DB_URL=${DATABASE_URL}
|
||||||
- STORAGE_PROVIDER=s3
|
- STORAGE_PROVIDER=s3
|
||||||
@ -46,11 +46,11 @@ services:
|
|||||||
# NextAuth配置从.env文件自动读取
|
# NextAuth配置从.env文件自动读取
|
||||||
# NEXTAUTH_URL=${NEXTAUTH_URL}
|
# NEXTAUTH_URL=${NEXTAUTH_URL}
|
||||||
# NEXTAUTH_SECRET=${NEXTAUTH_SECRET}
|
# NEXTAUTH_SECRET=${NEXTAUTH_SECRET}
|
||||||
- GEMINI_API_KEY=sk-NqhrkeBudn6pUtStnOuOnGyDfasogvFSsex4YgjDXJU3SK3E
|
- GEMINI_API_KEY=${GEMINI_API_KEY:-}
|
||||||
- GEMINI_API_BASE_URL=https://api.huandutech.com/v1
|
- GEMINI_API_BASE_URL=${GEMINI_API_BASE_URL:-}
|
||||||
- GEMINI_MODEL=gemini-2.0-flash-exp
|
- GEMINI_MODEL=${GEMINI_MODEL:-}
|
||||||
- DINGTALK_CLIENT_ID=ding1bs1gxwrcfu16x0f
|
- DINGTALK_CLIENT_ID=${DINGTALK_CLIENT_ID:-}
|
||||||
- DINGTALK_CLIENT_SECRET=qUmj_gBDzpPEYR3RA3hRqKzwu11a986O-wKfP8V5lDHXn7XJrXxW79KIeP5G-mcY
|
- DINGTALK_CLIENT_SECRET=${DINGTALK_CLIENT_SECRET:-}
|
||||||
volumes:
|
volumes:
|
||||||
- ./uploads:/app/uploads
|
- ./uploads:/app/uploads
|
||||||
healthcheck:
|
healthcheck:
|
||||||
|
|||||||
38
scripts/__tests__/deploy-registry-script.test.ts
Normal file
38
scripts/__tests__/deploy-registry-script.test.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import fs from 'fs'
|
||||||
|
import path from 'path'
|
||||||
|
|
||||||
|
describe('deploy-registry.ps1', () => {
|
||||||
|
const script = fs.readFileSync(
|
||||||
|
path.join(process.cwd(), 'deploy-registry.ps1'),
|
||||||
|
'utf8'
|
||||||
|
)
|
||||||
|
|
||||||
|
it('远程部署默认包含升级前数据库备份和 .env 备份', () => {
|
||||||
|
expect(script).toContain('[switch]$NoRemoteDbBackup')
|
||||||
|
expect(script).toContain('env_backup="$backup_dir/.env.before-upgrade"')
|
||||||
|
expect(script).toContain("docker compose -f \"$compose_file\" exec -T postgres sh -c 'pg_dump")
|
||||||
|
expect(script).toContain('test -s "$db_backup"')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('远程部署失败时默认会恢复旧 APP_IMAGE 或旧容器镜像', () => {
|
||||||
|
expect(script).toContain('[switch]$NoRollback')
|
||||||
|
expect(script).toContain('rollback()')
|
||||||
|
expect(script).toContain('trap rollback ERR')
|
||||||
|
expect(script).toContain('rollback_image="$old_container_image"')
|
||||||
|
expect(script).toContain('docker compose -f "$compose_file" up -d --no-deps --pull never --force-recreate "$compose_service" || true')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('部署后校验运行镜像、版本文件和数据库迁移状态', () => {
|
||||||
|
expect(script).toContain('verify_running_image')
|
||||||
|
expect(script).toContain('actual_image="$(docker inspect "$container_id" --format')
|
||||||
|
expect(script).toContain('运行镜像不匹配')
|
||||||
|
expect(script).toContain('运行版本不匹配')
|
||||||
|
expect(script).toContain('npx prisma migrate status --schema=prisma/schema.prisma')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('远端 shell 变量应以单引号字符串保留,避免被 PowerShell 提前展开', () => {
|
||||||
|
expect(script).toContain('$lines.Add(\'docker compose -f "$compose_file" up -d --pull never\')')
|
||||||
|
expect(script).toContain('$lines.Add(\'docker compose -f "$compose_file" ps\')')
|
||||||
|
expect(script).not.toContain('$lines.Add("docker compose -f `"$compose_file`"')
|
||||||
|
})
|
||||||
|
})
|
||||||
69
scripts/__tests__/docker-entrypoint.test.ts
Normal file
69
scripts/__tests__/docker-entrypoint.test.ts
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
import fs from 'fs'
|
||||||
|
import os from 'os'
|
||||||
|
import path from 'path'
|
||||||
|
import { spawnSync } from 'child_process'
|
||||||
|
|
||||||
|
describe('scripts/docker-entrypoint.sh', () => {
|
||||||
|
const entrypoint = fs.readFileSync(
|
||||||
|
path.join(process.cwd(), 'scripts/docker-entrypoint.sh'),
|
||||||
|
'utf8'
|
||||||
|
)
|
||||||
|
|
||||||
|
it('数据库迁移失败时必须停止容器启动', () => {
|
||||||
|
expect(entrypoint).toContain('npx prisma migrate deploy')
|
||||||
|
expect(entrypoint).toContain('数据库迁移失败')
|
||||||
|
expect(entrypoint).toMatch(/数据库迁移失败[\s\S]*exit 1/)
|
||||||
|
expect(entrypoint).not.toContain('数据库迁移失败,尝试继续启动')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('迁移命令真实失败时不会继续启动 server.js', () => {
|
||||||
|
if (process.platform !== 'linux') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'flnxi-entrypoint-'))
|
||||||
|
const binDir = path.join(tmpDir, 'bin')
|
||||||
|
fs.mkdirSync(binDir)
|
||||||
|
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(binDir, 'npx'),
|
||||||
|
[
|
||||||
|
'#!/bin/sh',
|
||||||
|
'if [ "$1 $2 $3" = "prisma db execute" ]; then',
|
||||||
|
' exit 0',
|
||||||
|
'fi',
|
||||||
|
'if [ "$1 $2 $3" = "prisma migrate deploy" ]; then',
|
||||||
|
' exit 42',
|
||||||
|
'fi',
|
||||||
|
'exit 1',
|
||||||
|
'',
|
||||||
|
].join('\n'),
|
||||||
|
{ mode: 0o755 }
|
||||||
|
)
|
||||||
|
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(binDir, 'node'),
|
||||||
|
[
|
||||||
|
'#!/bin/sh',
|
||||||
|
`echo started > "${path.join(tmpDir, 'server-started').replace(/\\/g, '/')}"`,
|
||||||
|
'exit 0',
|
||||||
|
'',
|
||||||
|
].join('\n'),
|
||||||
|
{ mode: 0o755 }
|
||||||
|
)
|
||||||
|
|
||||||
|
const scriptPath = path.join(process.cwd(), 'scripts/docker-entrypoint.sh')
|
||||||
|
const result = spawnSync('sh', [scriptPath], {
|
||||||
|
cwd: tmpDir,
|
||||||
|
env: {
|
||||||
|
...process.env,
|
||||||
|
PATH: `${binDir}${path.delimiter}${process.env.PATH || ''}`,
|
||||||
|
},
|
||||||
|
encoding: 'utf8',
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result.status).toBe(1)
|
||||||
|
expect(`${result.stdout}\n${result.stderr}`).toContain('数据库迁移失败')
|
||||||
|
expect(fs.existsSync(path.join(tmpDir, 'server-started'))).toBe(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
@ -1,5 +1,6 @@
|
|||||||
# 素材系统镜像构建脚本
|
# 素材系统镜像构建脚本(历史/内部备用)
|
||||||
# 支持测试版和正式版分离
|
# 当前客户交付主线请使用根目录 deploy-registry.ps1。
|
||||||
|
# 本脚本保留测试/正式分层的旧约定,但默认 Harbor/latest 口径不再作为客户升级依据。
|
||||||
|
|
||||||
param(
|
param(
|
||||||
[Parameter(Mandatory=$true)]
|
[Parameter(Mandatory=$true)]
|
||||||
@ -10,7 +11,8 @@ param(
|
|||||||
[string]$HarborHost = "harbor.flnxi.com",
|
[string]$HarborHost = "harbor.flnxi.com",
|
||||||
[string]$HarborProject = "flnxi",
|
[string]$HarborProject = "flnxi",
|
||||||
[switch]$Push = $false,
|
[switch]$Push = $false,
|
||||||
[switch]$NoBuild = $false
|
[switch]$NoBuild = $false,
|
||||||
|
[switch]$AcknowledgeLegacyHarborPush = $false
|
||||||
)
|
)
|
||||||
|
|
||||||
# Harbor 镜像路径规划:
|
# Harbor 镜像路径规划:
|
||||||
@ -30,6 +32,16 @@ param(
|
|||||||
|
|
||||||
$ErrorActionPreference = "Stop"
|
$ErrorActionPreference = "Stop"
|
||||||
|
|
||||||
|
Write-Host "[WARN] build-material-image.ps1 is a legacy/internal fallback entry." -ForegroundColor Yellow
|
||||||
|
Write-Host "[WARN] Customer release, upgrade, and rollback should use root deploy-registry.ps1 with a pinned APP_IMAGE tag." -ForegroundColor Yellow
|
||||||
|
Write-Host ""
|
||||||
|
|
||||||
|
if ($Push -and (-not $AcknowledgeLegacyHarborPush)) {
|
||||||
|
Write-Host "ERROR: this script still pushes to legacy Harbor/latest targets and must not be used as the customer release entry." -ForegroundColor Red
|
||||||
|
Write-Host "For internal legacy maintenance only, add -AcknowledgeLegacyHarborPush explicitly." -ForegroundColor Yellow
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
# 根据环境确定镜像路径
|
# 根据环境确定镜像路径
|
||||||
$ImageName = "flnxi-material"
|
$ImageName = "flnxi-material"
|
||||||
switch ($Environment) {
|
switch ($Environment) {
|
||||||
@ -78,6 +90,28 @@ try {
|
|||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# 优先加载本地基础镜像(避免网络问题)
|
||||||
|
$LocalImagePath = "docker-images/node/node-18-alpine.tar"
|
||||||
|
if (Test-Path $LocalImagePath) {
|
||||||
|
Write-Host "检查本地基础镜像..." -ForegroundColor Gray
|
||||||
|
$existingImage = docker images node:18-alpine --format "{{.ID}}" 2>$null
|
||||||
|
if (-not $existingImage) {
|
||||||
|
Write-Host "加载本地基础镜像: $LocalImagePath" -ForegroundColor Cyan
|
||||||
|
docker load -i $LocalImagePath
|
||||||
|
if ($LASTEXITCODE -eq 0) {
|
||||||
|
Write-Host "本地基础镜像加载成功 ✓" -ForegroundColor Green
|
||||||
|
} else {
|
||||||
|
Write-Host "警告: 本地基础镜像加载失败,将尝试在线拉取" -ForegroundColor Yellow
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Write-Host "本地基础镜像已存在 ✓" -ForegroundColor Green
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Write-Host "提示: 未找到本地基础镜像 ($LocalImagePath),将使用在线拉取" -ForegroundColor Yellow
|
||||||
|
Write-Host " 建议: 运行 'docker save node:18-alpine -o $LocalImagePath' 保存基础镜像" -ForegroundColor Gray
|
||||||
|
}
|
||||||
|
Write-Host ""
|
||||||
|
|
||||||
# 构建镜像
|
# 构建镜像
|
||||||
if (-not $NoBuild) {
|
if (-not $NoBuild) {
|
||||||
Write-Host "开始构建镜像..." -ForegroundColor Cyan
|
Write-Host "开始构建镜像..." -ForegroundColor Cyan
|
||||||
@ -149,10 +183,11 @@ Write-Host " 最新标签: $LatestImageName" -ForegroundColor Gray
|
|||||||
Write-Host ""
|
Write-Host ""
|
||||||
|
|
||||||
if ($Environment -eq "release") {
|
if ($Environment -eq "release") {
|
||||||
Write-Host "提示: 正式版镜像已发布,客户可通过以下地址拉取:" -ForegroundColor Yellow
|
Write-Host "NOTICE: release image built. Customer delivery must pin the full version image, not latest:" -ForegroundColor Yellow
|
||||||
Write-Host " $HarborHost/$HarborProject/material/flnxi-material:latest" -ForegroundColor White
|
Write-Host " $FullImageName" -ForegroundColor White
|
||||||
|
Write-Host "Current release/upgrade/rollback entry is root deploy-registry.ps1." -ForegroundColor Yellow
|
||||||
} else {
|
} else {
|
||||||
Write-Host "提示: 测试版镜像已发布到 material-dev 目录" -ForegroundColor Yellow
|
Write-Host "NOTICE: test image built under material-dev." -ForegroundColor Yellow
|
||||||
Write-Host " 测试完成后,使用以下命令发布正式版:" -ForegroundColor Gray
|
Write-Host " After testing, publish release with:" -ForegroundColor Gray
|
||||||
Write-Host " .\scripts\build-material-image.ps1 -Environment release -Version $Tag -Push" -ForegroundColor White
|
Write-Host " .\deploy-registry.ps1 -Action PublishRelease -Version $Tag -RegistryHost <registry-host> -RegistryPassword `$registryPassword" -ForegroundColor White
|
||||||
}
|
}
|
||||||
|
|||||||
@ -39,7 +39,9 @@ echo "[2/3] 执行数据库迁移..."
|
|||||||
if npx prisma migrate deploy; then
|
if npx prisma migrate deploy; then
|
||||||
echo "✓ 数据库迁移完成"
|
echo "✓ 数据库迁移完成"
|
||||||
else
|
else
|
||||||
echo "⚠ 数据库迁移失败,尝试继续启动..."
|
echo "✗ 数据库迁移失败,已停止启动以保护数据一致性"
|
||||||
|
echo "请先检查迁移日志、数据库连接和备份状态,修复后再重新启动容器"
|
||||||
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 3. 启动应用(系统初始化会在首次请求时自动执行)
|
# 3. 启动应用(系统初始化会在首次请求时自动执行)
|
||||||
|
|||||||
198
scripts/generate-shared-test-env.ps1
Normal file
198
scripts/generate-shared-test-env.ps1
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
param(
|
||||||
|
[string]$OutputPath,
|
||||||
|
[string]$ServerIp = "192.168.42.134",
|
||||||
|
[int]$AppPort = 4320,
|
||||||
|
[int]$MinioApiPort = 39200,
|
||||||
|
[int]$MinioConsolePort = 39201,
|
||||||
|
[int]$AgentPort = 3080,
|
||||||
|
[int]$AgentGoPort = 9091,
|
||||||
|
[string]$ComposeProjectName = "flnxi_sucai_test",
|
||||||
|
[string]$ContainerPrefix = "flnxi-sucai-test",
|
||||||
|
[string]$LicenseServerUrl,
|
||||||
|
[string]$LicenseProjectCode = "flnxi-sucai",
|
||||||
|
[string]$InitAdminEmail = "admin@test.flnxi.local"
|
||||||
|
)
|
||||||
|
|
||||||
|
Set-StrictMode -Version Latest
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
|
||||||
|
if ([string]::IsNullOrWhiteSpace($LicenseServerUrl)) {
|
||||||
|
throw "必须显式传入 -LicenseServerUrl,避免把过期或错误的许可证地址写进测试环境。"
|
||||||
|
}
|
||||||
|
|
||||||
|
function New-RandomString {
|
||||||
|
param(
|
||||||
|
[int]$Length = 32,
|
||||||
|
[string]$Alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||||
|
)
|
||||||
|
|
||||||
|
$bytes = New-Object byte[] ($Length * 2)
|
||||||
|
[System.Security.Cryptography.RandomNumberGenerator]::Fill($bytes)
|
||||||
|
|
||||||
|
$builder = New-Object System.Text.StringBuilder
|
||||||
|
foreach ($byte in $bytes) {
|
||||||
|
if ($builder.Length -ge $Length) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
[void]$builder.Append($Alphabet[$byte % $Alphabet.Length])
|
||||||
|
}
|
||||||
|
|
||||||
|
return $builder.ToString()
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-LicensePublicKey {
|
||||||
|
param([string]$ProjectRoot)
|
||||||
|
|
||||||
|
$candidateFiles = @(
|
||||||
|
(Join-Path $ProjectRoot "license-server\.env"),
|
||||||
|
(Join-Path $ProjectRoot ".env.production"),
|
||||||
|
(Join-Path $ProjectRoot ".env"),
|
||||||
|
(Join-Path $ProjectRoot ".env.local")
|
||||||
|
)
|
||||||
|
|
||||||
|
foreach ($file in $candidateFiles) {
|
||||||
|
if (-not (Test-Path $file)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
$content = Get-Content -Raw $file
|
||||||
|
|
||||||
|
$licenseMatch = [regex]::Match($content, '(?m)^LICENSE_PUBLIC_KEY=(.+)$')
|
||||||
|
if ($licenseMatch.Success) {
|
||||||
|
return $licenseMatch.Groups[1].Value.Trim().Trim('"')
|
||||||
|
}
|
||||||
|
|
||||||
|
$rsaMatch = [regex]::Match($content, '(?m)^RSA_PUBLIC_KEY=(.+)$')
|
||||||
|
if ($rsaMatch.Success) {
|
||||||
|
return $rsaMatch.Groups[1].Value.Trim().Trim('"')
|
||||||
|
}
|
||||||
|
|
||||||
|
$privateMatch = [regex]::Match($content, '(?m)^RSA_PRIVATE_KEY=(.+)$')
|
||||||
|
if ($privateMatch.Success) {
|
||||||
|
$privateKey = $privateMatch.Groups[1].Value.Trim().Trim('"')
|
||||||
|
$pem = $privateKey.Replace('\n', [Environment]::NewLine)
|
||||||
|
|
||||||
|
try {
|
||||||
|
$rsa = [System.Security.Cryptography.RSA]::Create()
|
||||||
|
try {
|
||||||
|
$rsa.ImportFromPem($pem.ToCharArray())
|
||||||
|
$publicPem = $rsa.ExportSubjectPublicKeyInfoPem()
|
||||||
|
return ([regex]::Replace($publicPem, "(\r\n|\n|\r)", '\n')).Trim()
|
||||||
|
} finally {
|
||||||
|
$rsa.Dispose()
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
throw "检测到 RSA_PRIVATE_KEY,但自动推导公钥失败:$($_.Exception.Message)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw "未找到可用的 LICENSE_PUBLIC_KEY / RSA_PUBLIC_KEY,无法生成测试环境 .env。"
|
||||||
|
}
|
||||||
|
|
||||||
|
if ([string]::IsNullOrWhiteSpace($OutputPath)) {
|
||||||
|
throw "必须提供 -OutputPath。"
|
||||||
|
}
|
||||||
|
|
||||||
|
$projectRoot = Split-Path -Parent $PSScriptRoot
|
||||||
|
$licensePublicKey = Get-LicensePublicKey -ProjectRoot $projectRoot
|
||||||
|
|
||||||
|
$postgresUser = "flnxi_test"
|
||||||
|
$postgresDb = "flnxi_sucai_test"
|
||||||
|
$postgresPassword = New-RandomString -Length 24
|
||||||
|
$minioRootUser = "flnxitest"
|
||||||
|
$minioRootPassword = New-RandomString -Length 24
|
||||||
|
$nextAuthSecret = New-RandomString -Length 48
|
||||||
|
$localEncryptionKey = New-RandomString -Length 48
|
||||||
|
$systemInitToken = New-RandomString -Length 40
|
||||||
|
$s3Bucket = "flnxi-sucai-test"
|
||||||
|
$appUrl = "http://${ServerIp}:${AppPort}"
|
||||||
|
$s3PublicDomain = "http://${ServerIp}:${MinioApiPort}/${s3Bucket}"
|
||||||
|
|
||||||
|
$envContent = @"
|
||||||
|
# FLNXI 素材库共享测试机环境变量
|
||||||
|
# 用途:192.168.42.134 测试机
|
||||||
|
# 生成时间:$(Get-Date -Format "yyyy-MM-dd HH:mm:ss")
|
||||||
|
|
||||||
|
NODE_ENV=production
|
||||||
|
|
||||||
|
# Shared-machine isolation
|
||||||
|
COMPOSE_PROJECT_NAME=$ComposeProjectName
|
||||||
|
APP_CONTAINER_NAME=${ContainerPrefix}-app
|
||||||
|
POSTGRES_CONTAINER_NAME=${ContainerPrefix}-postgres
|
||||||
|
MINIO_CONTAINER_NAME=${ContainerPrefix}-minio
|
||||||
|
MINIO_INIT_CONTAINER_NAME=${ContainerPrefix}-minio-init
|
||||||
|
POSTGRES_VOLUME_NAME=${ContainerPrefix}-postgres-data
|
||||||
|
MINIO_VOLUME_NAME=${ContainerPrefix}-minio-data
|
||||||
|
APP_NETWORK_NAME=${ContainerPrefix}-network
|
||||||
|
APP_PORT=$AppPort
|
||||||
|
MINIO_API_PORT=$MinioApiPort
|
||||||
|
MINIO_CONSOLE_PORT=$MinioConsolePort
|
||||||
|
AGENT_PORT=$AgentPort
|
||||||
|
AGENT_GO_PORT=$AgentGoPort
|
||||||
|
|
||||||
|
# Public URLs
|
||||||
|
SERVER_IP=$ServerIp
|
||||||
|
APP_PUBLIC_URL=$appUrl
|
||||||
|
NEXTAUTH_URL=$appUrl
|
||||||
|
NEXTAUTH_SECRET=$nextAuthSecret
|
||||||
|
|
||||||
|
# License
|
||||||
|
LICENSE_SERVER_URL=$LicenseServerUrl
|
||||||
|
LICENSE_PROJECT_CODE=$LicenseProjectCode
|
||||||
|
LICENSE_PUBLIC_KEY="$licensePublicKey"
|
||||||
|
LOCAL_ENCRYPTION_KEY=$localEncryptionKey
|
||||||
|
|
||||||
|
# Database
|
||||||
|
POSTGRES_USER=$postgresUser
|
||||||
|
POSTGRES_PASSWORD=$postgresPassword
|
||||||
|
POSTGRES_DB=$postgresDb
|
||||||
|
DATABASE_URL=postgresql://${postgresUser}:${postgresPassword}@postgres:5432/${postgresDb}
|
||||||
|
DATABASE_DRIVER=node
|
||||||
|
|
||||||
|
# Object storage
|
||||||
|
STORAGE_PROVIDER=s3
|
||||||
|
S3_ENDPOINT=http://minio:9000
|
||||||
|
S3_BUCKET=$s3Bucket
|
||||||
|
S3_ACCESS_KEY_ID=$minioRootUser
|
||||||
|
S3_SECRET_ACCESS_KEY=$minioRootPassword
|
||||||
|
S3_REGION=us-east-1
|
||||||
|
S3_PUBLIC_DOMAIN=$s3PublicDomain
|
||||||
|
S3_ENABLE_PATH_STYLE=1
|
||||||
|
S3_FILE_PATH=materials/
|
||||||
|
S3_ENV_PREFIX=test
|
||||||
|
MINIO_ROOT_USER=$minioRootUser
|
||||||
|
MINIO_ROOT_PASSWORD=$minioRootPassword
|
||||||
|
|
||||||
|
# Access control and URL rewriting
|
||||||
|
PUBLIC_DOMAINS=$ServerIp
|
||||||
|
NEXT_PUBLIC_PUBLIC_DOMAINS=$ServerIp
|
||||||
|
ALLOWED_ORIGINS=$appUrl
|
||||||
|
IMAGE_REMOTE_HOSTS=$ServerIp
|
||||||
|
|
||||||
|
# Initial system bootstrap
|
||||||
|
SYSTEM_INIT_TOKEN=$systemInitToken
|
||||||
|
INIT_ADMIN_EMAIL=$InitAdminEmail
|
||||||
|
INIT_ADMIN_PASSWORD=
|
||||||
|
|
||||||
|
# Optional features disabled by default on shared test server
|
||||||
|
DINGTALK_CLIENT_ID=
|
||||||
|
DINGTALK_CLIENT_SECRET=
|
||||||
|
NEXT_PUBLIC_DINGTALK_CALLBACK_URL=
|
||||||
|
GEMINI_API_KEY=
|
||||||
|
GEMINI_API_BASE_URL=https://api.huandutech.com/v1
|
||||||
|
GEMINI_MODEL=gemini-2.0-flash-exp
|
||||||
|
NEXT_PUBLIC_DEFAULT_HEADER_LOGO_URL=
|
||||||
|
NEXT_PUBLIC_DEFAULT_LOGIN_LOGO_URL=
|
||||||
|
NEXT_PUBLIC_INTRANET_URL=$appUrl
|
||||||
|
NEXT_PUBLIC_PORTAL_BASE_URL=$appUrl
|
||||||
|
UPLOAD_DIR=./uploads
|
||||||
|
"@
|
||||||
|
|
||||||
|
$parent = Split-Path -Parent $OutputPath
|
||||||
|
if ($parent -and -not (Test-Path $parent)) {
|
||||||
|
New-Item -ItemType Directory -Path $parent -Force | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
[System.IO.File]::WriteAllText($OutputPath, $envContent, [System.Text.UTF8Encoding]::new($false))
|
||||||
|
Write-Host "共享测试机环境文件已生成: $OutputPath"
|
||||||
132
发布管理.ps1
132
发布管理.ps1
@ -1,28 +1,42 @@
|
|||||||
# ================================================================
|
# ================================================================
|
||||||
# FLNXI 素材系统 - 发布管理工具 v2.0
|
# FLNXI 素材系统 - 历史内部发布管理工具 v2.0
|
||||||
# 功能:统一管理测试版部署、正式版发布、Agent 更新等操作
|
# 状态:已废弃,不作为客户安装、升级或正式发布入口
|
||||||
# 特性:进度条显示、健康检查、智能重试、趣味提示
|
# 当前正式发布入口请使用 deploy-registry.ps1
|
||||||
# ================================================================
|
# ================================================================
|
||||||
|
|
||||||
param(
|
param(
|
||||||
[switch]$Help
|
[switch]$Help,
|
||||||
|
[switch]$AcknowledgeLegacyInternalPublish
|
||||||
)
|
)
|
||||||
|
|
||||||
$ErrorActionPreference = "Stop"
|
$ErrorActionPreference = "Stop"
|
||||||
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
||||||
|
|
||||||
|
if (-not $Help -and -not $AcknowledgeLegacyInternalPublish) {
|
||||||
|
Write-Host "[ERROR] 发布管理.ps1 是历史内部发布控制台,默认禁止执行。" -ForegroundColor Red
|
||||||
|
Write-Host "当前客户/测试发布请使用 deploy-registry.ps1。" -ForegroundColor Yellow
|
||||||
|
Write-Host "该脚本包含旧服务器、旧 Harbor、许可证服务和 Docker 清理逻辑,不应作为客户交付入口。" -ForegroundColor Yellow
|
||||||
|
Write-Host "如确需排查历史链路,请追加 -AcknowledgeLegacyInternalPublish。" -ForegroundColor Yellow
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
# 配置
|
# 配置
|
||||||
$Config = @{
|
$Config = @{
|
||||||
Server = "45.249.208.118"
|
Server = ""
|
||||||
SshKey = "$env:USERPROFILE\.ssh\youyidian_prod_ed25519"
|
SshKey = ""
|
||||||
HarborHost = "harbor.flnxi.com"
|
HarborHost = ""
|
||||||
HarborProject = "flnxi" # 私有项目(素材系统)
|
HarborProject = "flnxi" # 公开项目(素材系统)
|
||||||
HarborPublicProject = "flnxi-public" # 公开项目(Agent、runtime)
|
HarborPublicProject = "flnxi-public" # 公开项目(Agent、runtime)
|
||||||
DockerHubUser = "flnxi"
|
DockerHubUser = "flnxi"
|
||||||
HealthCheckPort = 4320
|
HealthCheckPort = 4320
|
||||||
HealthCheckTimeout = 45
|
HealthCheckTimeout = 45
|
||||||
SshRetryCount = 3
|
SshRetryCount = 3
|
||||||
SshRetryDelay = 2
|
SshRetryDelay = 2
|
||||||
|
# 新许可证服务器配置
|
||||||
|
LicenseServer = ""
|
||||||
|
LicenseServerUser = "root"
|
||||||
|
LicenseServerSshKey = ""
|
||||||
|
LicenseServerPort = 9527
|
||||||
}
|
}
|
||||||
|
|
||||||
# 趣味提示
|
# 趣味提示
|
||||||
@ -118,6 +132,11 @@ function Get-CurrentVersion {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function Get-AgentVersion {
|
function Get-AgentVersion {
|
||||||
|
# 优先读取 Go 版本
|
||||||
|
if (Test-Path "flnxi-agent-go/VERSION.txt") {
|
||||||
|
return (Get-Content "flnxi-agent-go/VERSION.txt" -Raw).Trim()
|
||||||
|
}
|
||||||
|
# 兼容旧版 Node.js 版本
|
||||||
if (Test-Path "flnxi-agent/VERSION.txt") {
|
if (Test-Path "flnxi-agent/VERSION.txt") {
|
||||||
return (Get-Content "flnxi-agent/VERSION.txt" -Raw).Trim()
|
return (Get-Content "flnxi-agent/VERSION.txt" -Raw).Trim()
|
||||||
}
|
}
|
||||||
@ -246,7 +265,7 @@ function Show-Menu {
|
|||||||
Write-Color "║ [1] 构建并部署测试版 ║" "White"
|
Write-Color "║ [1] 构建并部署测试版 ║" "White"
|
||||||
Write-Color "║ ║" "Cyan"
|
Write-Color "║ ║" "Cyan"
|
||||||
Write-Color "║ === 正式发布 (需要确认) === ║" "Yellow"
|
Write-Color "║ === 正式发布 (需要确认) === ║" "Yellow"
|
||||||
Write-Color "║ [2] 发布素材系统正式版 (flnxi 私有) ⚠️ ║" "Yellow"
|
Write-Color "║ [2] 发布素材系统正式版 (flnxi 公开) ⚠️ ║" "Yellow"
|
||||||
Write-Color "║ [3] 发布 Agent (flnxi-public 公开) ⚠️ ║" "Yellow"
|
Write-Color "║ [3] 发布 Agent (flnxi-public 公开) ⚠️ ║" "Yellow"
|
||||||
Write-Color "║ ║" "Cyan"
|
Write-Color "║ ║" "Cyan"
|
||||||
Write-Color "║ === 基础设施 === ║" "White"
|
Write-Color "║ === 基础设施 === ║" "White"
|
||||||
@ -282,6 +301,27 @@ function Deploy-TestVersion {
|
|||||||
# 启用 BuildKit 以使用缓存加速
|
# 启用 BuildKit 以使用缓存加速
|
||||||
$env:DOCKER_BUILDKIT = "1"
|
$env:DOCKER_BUILDKIT = "1"
|
||||||
|
|
||||||
|
# 优先加载本地基础镜像(避免网络问题)
|
||||||
|
$LocalImagePath = "docker-images/node/node-18-alpine.tar"
|
||||||
|
if (Test-Path $LocalImagePath) {
|
||||||
|
Write-Info "检查本地基础镜像..."
|
||||||
|
$existingImage = docker images node:18-alpine --format "{{.ID}}" 2>$null
|
||||||
|
if (-not $existingImage) {
|
||||||
|
Write-Info "加载本地基础镜像: $LocalImagePath"
|
||||||
|
docker load -i $LocalImagePath
|
||||||
|
if ($LASTEXITCODE -eq 0) {
|
||||||
|
Write-Success "本地基础镜像加载成功"
|
||||||
|
} else {
|
||||||
|
Write-Warning "本地基础镜像加载失败,将尝试在线拉取"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Write-Success "本地基础镜像已存在"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Write-Warning "未找到本地基础镜像 ($LocalImagePath),将使用在线拉取"
|
||||||
|
Write-Info "建议: 运行 'docker save node:18-alpine -o $LocalImagePath' 保存基础镜像"
|
||||||
|
}
|
||||||
|
|
||||||
Write-Info "开始构建 (BuildKit 缓存已启用)..."
|
Write-Info "开始构建 (BuildKit 缓存已启用)..."
|
||||||
Write-Info "提示: 首次构建较慢,后续构建会利用缓存加速"
|
Write-Info "提示: 首次构建较慢,后续构建会利用缓存加速"
|
||||||
|
|
||||||
@ -369,6 +409,27 @@ function Publish-ReleaseVersion {
|
|||||||
# 启用 BuildKit
|
# 启用 BuildKit
|
||||||
$env:DOCKER_BUILDKIT = "1"
|
$env:DOCKER_BUILDKIT = "1"
|
||||||
|
|
||||||
|
# 优先加载本地基础镜像(避免网络问题)
|
||||||
|
$LocalImagePath = "docker-images/node/node-18-alpine.tar"
|
||||||
|
if (Test-Path $LocalImagePath) {
|
||||||
|
Write-Info "检查本地基础镜像..."
|
||||||
|
$existingImage = docker images node:18-alpine --format "{{.ID}}" 2>$null
|
||||||
|
if (-not $existingImage) {
|
||||||
|
Write-Info "加载本地基础镜像: $LocalImagePath"
|
||||||
|
docker load -i $LocalImagePath
|
||||||
|
if ($LASTEXITCODE -eq 0) {
|
||||||
|
Write-Success "本地基础镜像加载成功"
|
||||||
|
} else {
|
||||||
|
Write-Warning "本地基础镜像加载失败,将尝试在线拉取"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Write-Success "本地基础镜像已存在"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Write-Warning "未找到本地基础镜像 ($LocalImagePath),将使用在线拉取"
|
||||||
|
Write-Info "建议: 运行 'docker save node:18-alpine -o $LocalImagePath' 保存基础镜像"
|
||||||
|
}
|
||||||
|
|
||||||
# 构建正式版镜像
|
# 构建正式版镜像
|
||||||
Write-Info "构建正式版镜像 (BuildKit 缓存已启用)..."
|
Write-Info "构建正式版镜像 (BuildKit 缓存已启用)..."
|
||||||
$buildArgs = @(
|
$buildArgs = @(
|
||||||
@ -402,12 +463,13 @@ function Publish-ReleaseVersion {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# 3. 发布 Agent 安装程序
|
# 3. 发布 Agent 安装程序 (Go 版本)
|
||||||
function Publish-Agent {
|
function Publish-Agent {
|
||||||
Write-Title "发布 Agent 安装程序"
|
Write-Title "发布 Agent 安装程序 (Go 版本)"
|
||||||
|
|
||||||
$currentVersion = Get-AgentVersion
|
$currentVersion = Get-AgentVersion
|
||||||
Write-Info "当前 Agent 版本: $currentVersion"
|
Write-Info "当前 Agent 版本: $currentVersion"
|
||||||
|
Write-Info "技术栈: Go (镜像约 70MB)"
|
||||||
|
|
||||||
# Harbor 公开项目镜像配置
|
# Harbor 公开项目镜像配置
|
||||||
# 格式: harbor.flnxi.com/flnxi-public/agent:版本号
|
# 格式: harbor.flnxi.com/flnxi-public/agent:版本号
|
||||||
@ -441,16 +503,16 @@ function Publish-Agent {
|
|||||||
}
|
}
|
||||||
Write-Success "Harbor 登录状态: OK"
|
Write-Success "Harbor 登录状态: OK"
|
||||||
|
|
||||||
# 更新版本文件
|
# 更新版本文件 (Go 版本目录)
|
||||||
$newVersion | Out-File -FilePath "flnxi-agent/VERSION.txt" -Encoding UTF8 -NoNewline
|
$newVersion | Out-File -FilePath "flnxi-agent-go/VERSION.txt" -Encoding UTF8 -NoNewline
|
||||||
Write-Success "Agent 版本文件已更新: $newVersion"
|
Write-Success "Agent 版本文件已更新: $newVersion"
|
||||||
|
|
||||||
# 启用 BuildKit
|
# 启用 BuildKit
|
||||||
$env:DOCKER_BUILDKIT = "1"
|
$env:DOCKER_BUILDKIT = "1"
|
||||||
|
|
||||||
# 构建 Agent 镜像
|
# 构建 Agent 镜像 (Go 版本)
|
||||||
Write-Info "构建 Agent 镜像 (BuildKit 缓存已启用)..."
|
Write-Info "构建 Agent 镜像 (Go 版本, BuildKit 缓存已启用)..."
|
||||||
Push-Location "flnxi-agent"
|
Push-Location "flnxi-agent-go"
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$buildArgs = @(
|
$buildArgs = @(
|
||||||
@ -458,7 +520,6 @@ function Publish-Agent {
|
|||||||
"--progress=plain",
|
"--progress=plain",
|
||||||
"-t", "${harborRepo}:${newVersion}",
|
"-t", "${harborRepo}:${newVersion}",
|
||||||
"-t", "${harborRepo}:latest",
|
"-t", "${harborRepo}:latest",
|
||||||
"--build-arg", "AGENT_VERSION=$newVersion",
|
|
||||||
"."
|
"."
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -469,6 +530,10 @@ function Publish-Agent {
|
|||||||
}
|
}
|
||||||
Write-Success "构建完成"
|
Write-Success "构建完成"
|
||||||
|
|
||||||
|
# 显示镜像大小
|
||||||
|
$imageSize = docker images "${harborRepo}:${newVersion}" --format "{{.Size}}"
|
||||||
|
Write-Info "镜像大小: $imageSize"
|
||||||
|
|
||||||
# 推送到 Harbor flnxi-public 项目
|
# 推送到 Harbor flnxi-public 项目
|
||||||
Write-Info "推送到 Harbor (flnxi-public)..."
|
Write-Info "推送到 Harbor (flnxi-public)..."
|
||||||
docker push "${harborRepo}:${newVersion}"
|
docker push "${harborRepo}:${newVersion}"
|
||||||
@ -479,8 +544,12 @@ function Publish-Agent {
|
|||||||
Write-Host ""
|
Write-Host ""
|
||||||
Write-Color "═══════════════════════════════════════════════════════════" "Cyan"
|
Write-Color "═══════════════════════════════════════════════════════════" "Cyan"
|
||||||
Write-Info "镜像地址: ${harborRepo}:latest"
|
Write-Info "镜像地址: ${harborRepo}:latest"
|
||||||
|
Write-Info "镜像大小: $imageSize (Go 版本,比 Node.js 版本减少 ~70%)"
|
||||||
Write-Info "客户安装命令:"
|
Write-Info "客户安装命令:"
|
||||||
Write-Color " docker run -d -p 3080:3080 -v /var/run/docker.sock:/var/run/docker.sock ${harborRepo}:latest" "White"
|
Write-Color " docker run -d -p 9090:9090 -v /var/run/docker.sock:/var/run/docker.sock ${harborRepo}:latest" "White"
|
||||||
|
Write-Host ""
|
||||||
|
Write-Info "一键安装脚本:"
|
||||||
|
Write-Color " curl -sSL https://install.flnxi.com | bash" "White"
|
||||||
Write-Color "═══════════════════════════════════════════════════════════" "Cyan"
|
Write-Color "═══════════════════════════════════════════════════════════" "Cyan"
|
||||||
} else {
|
} else {
|
||||||
Write-Error "推送失败"
|
Write-Error "推送失败"
|
||||||
@ -872,7 +941,7 @@ function Show-Status {
|
|||||||
|
|
||||||
Write-Color "=== Harbor 项目结构 ===" "Yellow"
|
Write-Color "=== Harbor 项目结构 ===" "Yellow"
|
||||||
Write-Info "flnxi-public (公开): Agent、runtime 依赖"
|
Write-Info "flnxi-public (公开): Agent、runtime 依赖"
|
||||||
Write-Info "flnxi (私有): 素材系统镜像"
|
Write-Info "flnxi (公开): 素材系统镜像"
|
||||||
Write-Host ""
|
Write-Host ""
|
||||||
|
|
||||||
Write-Color "=== Harbor 镜像 ===" "Yellow"
|
Write-Color "=== Harbor 镜像 ===" "Yellow"
|
||||||
@ -885,7 +954,7 @@ function Show-Status {
|
|||||||
|
|
||||||
# 列出私有项目镜像
|
# 列出私有项目镜像
|
||||||
Write-Host ""
|
Write-Host ""
|
||||||
Write-Color "flnxi/ (私有 - 素材系统):" "White"
|
Write-Color "flnxi/ (公开 - 素材系统):" "White"
|
||||||
docker images "$($Config.HarborHost)/$($Config.HarborProject)/*" --format " {{.Repository}}:{{.Tag}}\t{{.CreatedSince}}" 2>$null
|
docker images "$($Config.HarborHost)/$($Config.HarborProject)/*" --format " {{.Repository}}:{{.Tag}}\t{{.CreatedSince}}" 2>$null
|
||||||
|
|
||||||
# 列出公开项目镜像
|
# 列出公开项目镜像
|
||||||
@ -912,26 +981,19 @@ function Show-Status {
|
|||||||
|
|
||||||
if ($Help) {
|
if ($Help) {
|
||||||
Write-Color "╔═══════════════════════════════════════════════════════════╗" "Cyan"
|
Write-Color "╔═══════════════════════════════════════════════════════════╗" "Cyan"
|
||||||
Write-Color "║ FLNXI 素材系统 - 发布管理工具 v2.0 ║" "Cyan"
|
Write-Color "║ FLNXI 素材系统 - 历史内部发布工具 v2.0 ║" "Cyan"
|
||||||
Write-Color "╠═══════════════════════════════════════════════════════════╣" "Cyan"
|
Write-Color "╠═══════════════════════════════════════════════════════════╣" "Cyan"
|
||||||
Write-Color "║ 用法: ║" "White"
|
Write-Color "║ 用法: ║" "White"
|
||||||
Write-Color "║ .\发布管理.ps1 启动交互式菜单 ║" "Gray"
|
Write-Color "║ .\deploy-registry.ps1 当前版本化发布入口 ║" "Gray"
|
||||||
|
Write-Color "║ .\发布管理.ps1 -AcknowledgeLegacyInternalPublish ║" "Gray"
|
||||||
|
Write-Color "║ 仅用于历史链路排查 ║" "Gray"
|
||||||
Write-Color "║ .\发布管理.ps1 -Help 显示此帮助 ║" "Gray"
|
Write-Color "║ .\发布管理.ps1 -Help 显示此帮助 ║" "Gray"
|
||||||
Write-Color "╠═══════════════════════════════════════════════════════════╣" "Cyan"
|
Write-Color "╠═══════════════════════════════════════════════════════════╣" "Cyan"
|
||||||
Write-Color "║ 功能: ║" "White"
|
Write-Color "║ 说明: ║" "White"
|
||||||
Write-Color "║ [1] 构建并部署测试版 (含健康检查) ║" "White"
|
Write-Color "║ 该脚本包含旧服务器、旧 Harbor、latest 和内部清理逻辑, ║" "Gray"
|
||||||
Write-Color "║ [2] 发布素材系统正式版 (需确认) ⚠️ ║" "Yellow"
|
Write-Color "║ 不作为客户安装、升级或正式发布入口。 ║" "Gray"
|
||||||
Write-Color "║ [3] 发布 Agent 安装程序 (需确认) ⚠️ ║" "Yellow"
|
|
||||||
Write-Color "║ [4] 同步 Runtime 镜像 (国内镜像源加速) ║" "White"
|
|
||||||
Write-Color "║ [5] 部署许可证服务器 (需确认) ⚠️ ║" "Yellow"
|
|
||||||
Write-Color "║ [6] 查看版本和镜像状态 ║" "White"
|
|
||||||
Write-Color "║ [7] 清理本地旧镜像 ║" "White"
|
|
||||||
Write-Color "║ [8] 清理服务器旧镜像 ║" "White"
|
|
||||||
Write-Color "║ [9] 深度清理 Docker (释放磁盘空间) ║" "Red"
|
|
||||||
Write-Color "╠═══════════════════════════════════════════════════════════╣" "Cyan"
|
Write-Color "╠═══════════════════════════════════════════════════════════╣" "Cyan"
|
||||||
Write-Color "║ 特性: ║" "White"
|
Write-Color "║ 当前入口: deploy-registry.ps1 + docker-compose.prod.yml ║" "Green"
|
||||||
Write-Color "║ ✓ 进度条显示 ✓ 健康检查 ✓ SSH智能重试 ║" "Green"
|
|
||||||
Write-Color "║ ✓ 国内镜像源加速 ✓ 趣味提示 ✓ 镜像清理 ║" "Green"
|
|
||||||
Write-Color "╚═══════════════════════════════════════════════════════════╝" "Cyan"
|
Write-Color "╚═══════════════════════════════════════════════════════════╝" "Cyan"
|
||||||
exit 0
|
exit 0
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user