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

470 lines
16 KiB
PowerShell
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.

# ================================================================
# 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 "所有操作完成!"