- 修复内置模板验证问题(builtin-video-material等) - 添加模板验证工具 lib/template-validator.ts - 创建模板系统优化需求文档(30个详细需求) - 优化上传API的模板验证逻辑 - 改进错误提示信息的清晰度 - 添加字段池和分类管理相关需求 - 完善自定义字段的创建、验证、存储逻辑 - 清理过期的 .specstory 和 .trae 文件
470 lines
16 KiB
PowerShell
470 lines
16 KiB
PowerShell
# ================================================================
|
||
# FLNXI素材库 - 推送正式版镜像到 Harbor
|
||
# 用途:构建并推送正式版镜像到 Harbor 公开仓库
|
||
# 参考:deploy-new-fixed.ps1 的构建和推送逻辑
|
||
# ================================================================
|
||
|
||
param(
|
||
[string]$HarborUrl = "harbor.flnxi.com",
|
||
[string]$HarborProjectName = "flnxi",
|
||
[string]$ImageName = "flnxi-material",
|
||
[string]$Tag = "pro-latest",
|
||
[string]$HarborUsername = "admin",
|
||
[string]$HarborPassword = "Harbor12345",
|
||
[switch]$SkipBuild,
|
||
[switch]$Help
|
||
)
|
||
|
||
# 脚本配置
|
||
Set-StrictMode -Version Latest
|
||
$ErrorActionPreference = "Continue"
|
||
try {
|
||
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
||
if ($PSVersionTable.PSVersion.Major -ge 6) {
|
||
$PSDefaultParameterValues['*:Encoding'] = 'utf8'
|
||
}
|
||
} catch {
|
||
# 控制台编码设置失败时忽略
|
||
}
|
||
|
||
# 颜色输出函数
|
||
function Write-ColorOutput {
|
||
param(
|
||
[string]$Message,
|
||
[ConsoleColor]$ForegroundColor = "White"
|
||
)
|
||
Write-Host $Message -ForegroundColor $ForegroundColor
|
||
}
|
||
|
||
function Write-Success {
|
||
param([string]$Message)
|
||
Write-ColorOutput "[OK] $Message" "Green"
|
||
}
|
||
|
||
function Write-Warning {
|
||
param([string]$Message)
|
||
Write-ColorOutput "[WARN] $Message" "Yellow"
|
||
}
|
||
|
||
function Write-ErrorColored {
|
||
param([string]$Message)
|
||
Write-ColorOutput "[ERROR] $Message" "Red"
|
||
}
|
||
|
||
function Write-Info {
|
||
param([string]$Message)
|
||
Write-ColorOutput "[INFO] $Message" "Cyan"
|
||
}
|
||
|
||
# 显示帮助
|
||
if ($Help) {
|
||
Write-ColorOutput "推送正式版镜像到 Harbor" "Green"
|
||
Write-ColorOutput "========================================" "Green"
|
||
Write-ColorOutput "用法:" "White"
|
||
Write-ColorOutput " .\push-to-harbor.ps1 [参数]" "Yellow"
|
||
Write-ColorOutput "" "White"
|
||
Write-ColorOutput "参数:" "White"
|
||
Write-ColorOutput " -HarborUrl [地址] Harbor地址 (默认: harbor.flnxi.com)" "Gray"
|
||
Write-ColorOutput " -HarborProjectName [名称] Harbor项目名称 (默认: flnxi)" "Gray"
|
||
Write-ColorOutput " -ImageName [名称] 镜像名称 (默认: flnxi-material)" "Gray"
|
||
Write-ColorOutput " -Tag [标签] 镜像标签 (默认: pro-latest)" "Gray"
|
||
Write-ColorOutput " -HarborUsername [用户] Harbor用户名 (默认: admin)" "Gray"
|
||
Write-ColorOutput " -HarborPassword [密码] Harbor密码 (默认: Harbor12345)" "Gray"
|
||
Write-ColorOutput " -SkipBuild 跳过构建,直接推送现有镜像" "Gray"
|
||
Write-ColorOutput " -Help 显示此帮助信息" "Gray"
|
||
Write-ColorOutput "" "White"
|
||
Write-ColorOutput "示例:" "White"
|
||
Write-ColorOutput " .\push-to-harbor.ps1" "Yellow"
|
||
Write-ColorOutput " .\push-to-harbor.ps1 -SkipBuild" "Yellow"
|
||
Write-ColorOutput " .\push-to-harbor.ps1 -Tag pro-v1.0.0" "Yellow"
|
||
exit 0
|
||
}
|
||
|
||
Write-ColorOutput "========================================" "Magenta"
|
||
Write-ColorOutput " 推送正式版镜像到 Harbor" "Magenta"
|
||
Write-ColorOutput "========================================" "Magenta"
|
||
Write-ColorOutput ""
|
||
|
||
# 检查 Docker
|
||
Write-Info "检查 Docker 环境..."
|
||
if (-not (Get-Command docker -ErrorAction SilentlyContinue)) {
|
||
Write-ErrorColored "未找到 Docker 命令,请先安装 Docker"
|
||
exit 1
|
||
}
|
||
|
||
$dockerCheck = docker ps 2>&1
|
||
if ($LASTEXITCODE -ne 0) {
|
||
Write-ErrorColored "Docker 未运行,请启动 Docker Desktop"
|
||
exit 1
|
||
}
|
||
Write-Success "Docker 环境正常"
|
||
|
||
# 项目根目录
|
||
$projectRoot = Get-Location
|
||
Write-Info "项目目录: $projectRoot"
|
||
|
||
# 本地镜像名称和目标镜像
|
||
$localImage = "${ImageName}:${Tag}"
|
||
$registryImage = "${HarborUrl}/${HarborProjectName}/${ImageName}:${Tag}"
|
||
|
||
Write-Info "配置信息:"
|
||
Write-Info " Harbor地址: $HarborUrl"
|
||
Write-Info " Harbor项目: $HarborProjectName"
|
||
Write-Info " 本地镜像: $localImage"
|
||
Write-Info " 目标镜像: $registryImage"
|
||
Write-ColorOutput ""
|
||
|
||
# 准备构建环境(参考 deploy-new-fixed.ps1)
|
||
function Prepare-BuildEnvironment {
|
||
param(
|
||
[string]$ProjectRoot,
|
||
[string]$Server = "localhost"
|
||
)
|
||
|
||
$state = @{
|
||
TargetPath = Join-Path $ProjectRoot ".env.production"
|
||
BackupPath = $null
|
||
SourcePath = $null
|
||
CreatedTemp = $false
|
||
}
|
||
|
||
$targetPath = $state.TargetPath
|
||
$existingTarget = Test-Path $targetPath
|
||
|
||
if ($existingTarget) {
|
||
Write-Info "检测到现有 .env.production,直接使用"
|
||
return $state
|
||
}
|
||
|
||
# 查找可用的环境变量文件
|
||
$candidatePaths = @(
|
||
(Join-Path $ProjectRoot ".env.production.local"),
|
||
(Join-Path $ProjectRoot "config-bianliang.env"),
|
||
(Join-Path $ProjectRoot ".env.local")
|
||
)
|
||
|
||
foreach ($candidate in $candidatePaths) {
|
||
if (-not [string]::IsNullOrWhiteSpace($candidate) -and (Test-Path $candidate)) {
|
||
$state.SourcePath = (Resolve-Path $candidate).Path
|
||
Write-Info "使用环境变量文件: $($state.SourcePath)"
|
||
break
|
||
}
|
||
}
|
||
|
||
if ($state.SourcePath) {
|
||
if ($existingTarget) {
|
||
$state.BackupPath = "$targetPath.deploy-backup"
|
||
Copy-Item -Path $targetPath -Destination $state.BackupPath -Force
|
||
}
|
||
if ($state.SourcePath -ne $targetPath) {
|
||
Copy-Item -Path $state.SourcePath -Destination $targetPath -Force
|
||
$state.CreatedTemp = $true
|
||
}
|
||
return $state
|
||
}
|
||
|
||
Write-Warning "未找到可用的环境变量文件,将生成临时占位配置"
|
||
|
||
$placeholder = @"
|
||
# 生产环境配置 (临时生成)
|
||
NODE_ENV=production
|
||
NEXT_TELEMETRY_DISABLED=1
|
||
|
||
# 数据库配置 (占位符)
|
||
DATABASE_URL=postgresql://placeholder:placeholder@localhost:5432/placeholder
|
||
|
||
# 应用URL配置
|
||
NEXTAUTH_URL=http://$Server:4320
|
||
APP_PUBLIC_URL=http://$Server:4320
|
||
|
||
# 安全配置
|
||
NEXTAUTH_SECRET=$(New-Guid)
|
||
|
||
# 存储配置
|
||
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/
|
||
|
||
# 本地上传目录
|
||
UPLOAD_DIR=./uploads
|
||
"@
|
||
|
||
$placeholder | Out-File -FilePath $targetPath -Encoding UTF8 -Force
|
||
$state.CreatedTemp = -not $existingTarget
|
||
return $state
|
||
}
|
||
|
||
function Restore-BuildEnvironment {
|
||
param($State)
|
||
if (-not $State) {
|
||
return
|
||
}
|
||
|
||
$targetPath = $State.TargetPath
|
||
if ($State.BackupPath -and (Test-Path $State.BackupPath)) {
|
||
Move-Item -Path $State.BackupPath -Destination $targetPath -Force
|
||
return
|
||
}
|
||
|
||
if ($State.CreatedTemp -and (Test-Path $targetPath)) {
|
||
Remove-Item -Path $targetPath -Force -ErrorAction SilentlyContinue
|
||
}
|
||
}
|
||
|
||
# 加载基础镜像函数(参考 deploy-new-fixed.ps1)
|
||
function Load-BaseImages {
|
||
param(
|
||
[string]$ProjectRoot,
|
||
[string]$RegistryUrl = "",
|
||
[string]$RegistryType = "harbor",
|
||
[string]$RegistryProject = ""
|
||
)
|
||
|
||
# 基础镜像列表
|
||
$baseImages = @(
|
||
"node:18-alpine"
|
||
)
|
||
|
||
$loadedCount = 0
|
||
$pulledCount = 0
|
||
$skippedCount = 0
|
||
|
||
Write-Info "检查基础镜像..."
|
||
foreach ($baseImage in $baseImages) {
|
||
# 1. 优先检查本地是否已有该镜像(最快,直接使用)
|
||
$localCheck = docker images --format "{{.Repository}}:{{.Tag}}" | Select-String -Pattern "^${baseImage}$"
|
||
if ($localCheck) {
|
||
Write-Info " [✓] 本地已存在,直接使用: $baseImage"
|
||
$skippedCount++
|
||
continue
|
||
}
|
||
|
||
# 2. 如果本地没有,尝试从Registry拉取(避免从Docker Hub下载)
|
||
if ($RegistryUrl) {
|
||
$imageName = $baseImage -replace ":", "-"
|
||
if ($RegistryType -eq "harbor") {
|
||
$registryBaseImage = "${RegistryUrl}/${RegistryProject}/base-${imageName}:latest"
|
||
} else {
|
||
$registryBaseImage = "${RegistryUrl}/youyidian/base-${imageName}:latest"
|
||
}
|
||
|
||
Write-Info " 尝试从Registry拉取: $registryBaseImage"
|
||
$pullResult = docker pull $registryBaseImage 2>&1
|
||
if ($LASTEXITCODE -eq 0) {
|
||
# 标记为原始名称
|
||
docker tag $registryBaseImage $baseImage 2>&1 | Out-Null
|
||
Write-Info " [OK] 已从Registry拉取并标记: $baseImage"
|
||
$pulledCount++
|
||
continue
|
||
} else {
|
||
Write-Info " [跳过] Registry中不存在"
|
||
}
|
||
}
|
||
|
||
# 3. 如果Registry也没有,尝试从本地文件加载
|
||
$imagesDir = Join-Path $ProjectRoot "docker-images"
|
||
if (Test-Path $imagesDir) {
|
||
$foundInFiles = $false
|
||
Get-ChildItem $imagesDir -Directory -ErrorAction SilentlyContinue | ForEach-Object {
|
||
if ($foundInFiles) { return }
|
||
$imageDir = $_.FullName
|
||
Get-ChildItem $imageDir -Filter "*.tar" -ErrorAction SilentlyContinue | ForEach-Object {
|
||
if ($foundInFiles) { return }
|
||
$tarFile = $_.FullName
|
||
Write-Info " 尝试从本地文件加载: $($_.Name)"
|
||
$result = docker load -i $tarFile 2>&1
|
||
if ($LASTEXITCODE -eq 0) {
|
||
$loadedImage = $result | Select-String -Pattern "Loaded image: (.+)" | ForEach-Object { $_.Matches[0].Groups[1].Value }
|
||
if ($loadedImage -and $loadedImage -match $baseImage) {
|
||
Write-Info " [OK] 已从本地文件加载: $loadedImage"
|
||
$loadedCount++
|
||
$foundInFiles = $true
|
||
}
|
||
}
|
||
}
|
||
}
|
||
if ($foundInFiles) {
|
||
continue
|
||
}
|
||
}
|
||
|
||
# 4. 如果以上都没有,构建时会从网络下载(Docker会自动处理)
|
||
Write-Info " [提示] 未找到基础镜像,构建时将尝试从网络下载: $baseImage"
|
||
}
|
||
|
||
if ($skippedCount -gt 0) {
|
||
Write-Success "本地已有 $skippedCount 个基础镜像,直接使用"
|
||
}
|
||
if ($pulledCount -gt 0) {
|
||
Write-Success "从Registry拉取 $pulledCount 个基础镜像"
|
||
}
|
||
if ($loadedCount -gt 0) {
|
||
Write-Success "从本地文件加载 $loadedCount 个基础镜像"
|
||
}
|
||
}
|
||
|
||
# 1. 加载基础镜像(在构建前)
|
||
if (-not $SkipBuild) {
|
||
Write-ColorOutput "===> 加载基础镜像" "Cyan"
|
||
Load-BaseImages -ProjectRoot $projectRoot -RegistryUrl $HarborUrl -RegistryType "harbor" -RegistryProject $HarborProjectName
|
||
Write-ColorOutput ""
|
||
}
|
||
|
||
# 2. 构建镜像(如果需要)
|
||
if (-not $SkipBuild) {
|
||
Write-ColorOutput "===> 构建镜像阶段" "Cyan"
|
||
Write-Info "开始构建 Docker 镜像: $localImage"
|
||
|
||
$envState = Prepare-BuildEnvironment -ProjectRoot $projectRoot -Server "localhost"
|
||
|
||
try {
|
||
# 构建参数(参考 deploy-new-fixed.ps1)
|
||
# 为了避免使用旧的构建缓存,这里强制禁用 Docker Build 缓存
|
||
# 确保每次推送前都会重新编译 .next 产物,包含最新的 API 逻辑和日志
|
||
$buildArgs = @(
|
||
"build",
|
||
"--no-cache",
|
||
"-t", $localImage,
|
||
"-f", "Dockerfile",
|
||
"--build-arg", "DATABASE_URL=postgresql://placeholder:placeholder@localhost:5432/placeholder",
|
||
"--build-arg", "NEXTAUTH_URL=http://localhost:4320",
|
||
"--build-arg", "APP_PUBLIC_URL=http://localhost:4320",
|
||
"."
|
||
)
|
||
|
||
Write-Info "执行构建命令..."
|
||
$buildProcess = Start-Process -FilePath "docker" -ArgumentList $buildArgs -NoNewWindow -PassThru -Wait
|
||
|
||
if ($buildProcess.ExitCode -eq 0) {
|
||
Write-Success "Docker构建成功: $localImage"
|
||
|
||
# 验证镜像
|
||
$inspectProcess = Start-Process -FilePath "docker" -ArgumentList @("image", "inspect", $localImage) -NoNewWindow -PassThru -Wait
|
||
if ($inspectProcess.ExitCode -eq 0) {
|
||
Write-Success "镜像验证成功"
|
||
} else {
|
||
Write-ErrorColored "镜像验证失败"
|
||
exit 1
|
||
}
|
||
} else {
|
||
Write-ErrorColored "Docker构建失败"
|
||
exit 1
|
||
}
|
||
} catch {
|
||
Write-ErrorColored "构建过程异常: $_"
|
||
exit 1
|
||
} finally {
|
||
Restore-BuildEnvironment -State $envState
|
||
}
|
||
} else {
|
||
Write-Info "跳过构建,使用现有镜像: $localImage"
|
||
|
||
# 检查镜像是否存在
|
||
$imageCheck = docker images --format "{{.Repository}}:{{.Tag}}" | Select-String -Pattern "^${ImageName}:${Tag}$"
|
||
if (-not $imageCheck) {
|
||
Write-ErrorColored "未找到镜像 $localImage,请先构建或移除 -SkipBuild 参数"
|
||
exit 1
|
||
}
|
||
Write-Success "找到现有镜像: $localImage"
|
||
}
|
||
|
||
# 3. 登录 Harbor(参考 deploy-new-fixed.ps1)
|
||
Write-ColorOutput ""
|
||
Write-ColorOutput "===> 登录 Harbor" "Cyan"
|
||
Write-Info "正在登录 Harbor: $HarborUrl"
|
||
|
||
try {
|
||
# 使用 PowerShell 的方式传递密码(参考 deploy-new-fixed.ps1)
|
||
$securePassword = ConvertTo-SecureString $HarborPassword -AsPlainText -Force
|
||
$credential = New-Object System.Management.Automation.PSCredential($HarborUsername, $securePassword)
|
||
$passwordPlain = $credential.GetNetworkCredential().Password
|
||
|
||
$loginProcess = Start-Process -FilePath "docker" -ArgumentList @("login", $HarborUrl, "-u", $HarborUsername, "-p", $passwordPlain) -NoNewWindow -PassThru -Wait
|
||
|
||
if ($loginProcess.ExitCode -eq 0) {
|
||
Write-Success "Harbor 登录成功"
|
||
} else {
|
||
Write-ErrorColored "Harbor 登录失败"
|
||
exit 1
|
||
}
|
||
} catch {
|
||
Write-ErrorColored "登录过程异常: $_"
|
||
exit 1
|
||
}
|
||
|
||
# 4. 标记镜像(参考 deploy-new-fixed.ps1)
|
||
Write-ColorOutput ""
|
||
Write-ColorOutput "===> 标记镜像" "Cyan"
|
||
Write-Info "标记镜像: $localImage -> $registryImage"
|
||
|
||
try {
|
||
$tagProcess = Start-Process -FilePath "docker" -ArgumentList @("tag", $localImage, $registryImage) -NoNewWindow -PassThru -Wait
|
||
|
||
if ($tagProcess.ExitCode -eq 0) {
|
||
Write-Success "镜像标记成功"
|
||
} else {
|
||
Write-ErrorColored "镜像标记失败"
|
||
exit 1
|
||
}
|
||
} catch {
|
||
Write-ErrorColored "标记过程异常: $_"
|
||
exit 1
|
||
}
|
||
|
||
# 5. 推送镜像(参考 deploy-new-fixed.ps1,带重试机制)
|
||
Write-ColorOutput ""
|
||
Write-ColorOutput "===> 推送镜像到 Harbor" "Cyan"
|
||
Write-Info "正在推送镜像: $registryImage"
|
||
Write-Info "这可能需要几分钟时间,请耐心等待..."
|
||
|
||
$pushMaxRetries = 3
|
||
$pushRetryDelay = 5
|
||
$pushAttempt = 0
|
||
$pushSuccess = $false
|
||
|
||
while ($pushAttempt -lt $pushMaxRetries -and -not $pushSuccess) {
|
||
$pushAttempt++
|
||
if ($pushAttempt -gt 1) {
|
||
Write-Info "镜像推送重试 ($pushAttempt/$pushMaxRetries)..."
|
||
Start-Sleep -Seconds $pushRetryDelay
|
||
}
|
||
|
||
try {
|
||
$pushProcess = Start-Process -FilePath "docker" -ArgumentList @("push", $registryImage) -NoNewWindow -PassThru -Wait
|
||
|
||
if ($pushProcess.ExitCode -eq 0) {
|
||
Write-Success "镜像推送成功: $registryImage"
|
||
$pushSuccess = $true
|
||
} else {
|
||
if ($pushAttempt -lt $pushMaxRetries) {
|
||
Write-Warning "镜像推送失败 (尝试 $pushAttempt/$pushMaxRetries),将重试..."
|
||
} else {
|
||
Write-ErrorColored "镜像推送失败,已重试 $pushMaxRetries 次"
|
||
exit 1
|
||
}
|
||
}
|
||
} catch {
|
||
if ($pushAttempt -lt $pushMaxRetries) {
|
||
Write-Warning "推送过程异常 (尝试 $pushAttempt/$pushMaxRetries): $_,将重试..."
|
||
} else {
|
||
Write-ErrorColored "推送过程异常,已重试 $pushMaxRetries 次: $_"
|
||
exit 1
|
||
}
|
||
}
|
||
}
|
||
|
||
Write-ColorOutput ""
|
||
Write-ColorOutput "========================================" "Green"
|
||
Write-ColorOutput " 推送完成!" "Green"
|
||
Write-ColorOutput "========================================" "Green"
|
||
Write-ColorOutput ""
|
||
Write-Info "镜像地址: $registryImage"
|
||
Write-Info "现在可以在安装脚本中使用此镜像了"
|
||
Write-Success "所有操作完成!"
|
||
|