param( [string]$FnpackPath = "", [switch]$SkipImageExport ) $ErrorActionPreference = "Stop" $root = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path $project = Join-Path $root "fnos\havline" $version = (Get-Content -Raw (Join-Path $root "havline\VERSION")).Trim() function Resolve-DockerCli { if ($env:DOCKER_BIN) { return $env:DOCKER_BIN } $command = Get-Command docker -ErrorAction SilentlyContinue if ($command) { return $command.Source } $default = "C:\Program Files\Docker\Docker\resources\bin\docker.exe" if (Test-Path -LiteralPath $default) { return $default } throw "找不到 docker 命令" } function Export-OfflineImage { param( [string]$DockerBin, [string]$BuildContext, [string]$Platform, [string]$Arch, [string]$Version, [string]$OutputDir ) $tarName = "havline-$Version-$Arch.tar" $tar = Join-Path $OutputDir $tarName $gz = Join-Path $OutputDir "havline-$Version-$Arch.tar.gz" Push-Location $BuildContext try { & $DockerBin buildx build ` --platform $Platform ` --build-arg VERSION_FILE=VERSION ` --provenance=false ` --sbom=false ` --output "type=docker,name=havline:$Version,dest=$tar" ` . if ($LASTEXITCODE -ne 0) { throw "导出 $Arch 镜像失败,退出码:$LASTEXITCODE" } } finally { Pop-Location } Compress-GzipFile -Source $tar -Destination $gz Remove-Item -LiteralPath $tar -Force Write-Output "镜像归档:$gz" } function Compress-GzipFile { param( [string]$Source, [string]$Destination ) $sourceStream = [System.IO.File]::OpenRead($Source) try { $targetStream = [System.IO.File]::Create($Destination) try { $gzip = New-Object System.IO.Compression.GZipStream($targetStream, [System.IO.Compression.CompressionLevel]::Optimal) try { $sourceStream.CopyTo($gzip) } finally { $gzip.Dispose() } } finally { $targetStream.Dispose() } } finally { $sourceStream.Dispose() } } if (-not (Test-Path -LiteralPath $project)) { throw "找不到飞牛应用工程:$project" } # 版本一致性校验:Docker 版离线镜像标签为 havline:<主仓 VERSION>, # 而容器运行时按 ${TRIM_APPVER}=manifest.version 加载镜像,两者必须严格一致。 $manifestVersion = ([regex]::Match((Get-Content -Raw (Join-Path $project "manifest")), '(?m)^version\s*=\s*(\S+)')).Groups[1].Value if (-not $manifestVersion) { throw "Docker manifest 缺少 version 字段:$(Join-Path $project 'manifest')" } Write-Output "App version: $version; Manifest version: $manifestVersion" if ($manifestVersion -ne $version) { throw "版本不一致:主仓 VERSION=$version,Docker manifest=$manifestVersion。镜像标签与运行时加载版本不匹配,请先将 fnos\havline\manifest 的 version 同步为主仓 VERSION。" } if (-not $FnpackPath) { $localTool = Join-Path $PSScriptRoot "fnpack.exe" $FnpackPath = if (Test-Path -LiteralPath $localTool) { $localTool } else { "fnpack" } } $stamp = Get-Date -Format "yyyyMMddHHmmss" $stage = Join-Path $PSScriptRoot ".build\havline-$stamp" New-Item -ItemType Directory -Force $stage | Out-Null robocopy $project $stage /E /XF .DS_Store /NFL /NDL /NJH /NJS /NP | Out-Null if ($LASTEXITCODE -ge 8) { throw "复制飞牛应用工程失败,robocopy 退出码:$LASTEXITCODE" } $imageDir = Join-Path $stage "app\docker" New-Item -ItemType Directory -Force $imageDir | Out-Null if (-not $SkipImageExport) { $dockerBin = Resolve-DockerCli $buildContext = Join-Path $root "havline" Export-OfflineImage -DockerBin $dockerBin -BuildContext $buildContext -Platform "linux/amd64" -Arch "amd64" -Version $version -OutputDir $imageDir Export-OfflineImage -DockerBin $dockerBin -BuildContext $buildContext -Platform "linux/arm64" -Arch "arm64" -Version $version -OutputDir $imageDir } else { foreach ($arch in @("amd64", "arm64")) { $required = Join-Path $imageDir "havline-$version-$arch.tar.gz" if (-not (Test-Path -LiteralPath $required)) { throw "缺少离线镜像:$required" } } } Push-Location $PSScriptRoot try { & $FnpackPath build -d $stage if ($LASTEXITCODE -ne 0) { throw "fnpack build 失败,退出码:$LASTEXITCODE" } $output = Join-Path $PSScriptRoot "havline.fpk" $dist = Join-Path $PSScriptRoot "dist" New-Item -ItemType Directory -Force $dist | Out-Null Copy-Item -LiteralPath $output -Destination (Join-Path $dist "havline-$version.fpk") -Force Write-Output "FPK: $(Join-Path $dist "havline-$version.fpk")" $sizeMB = [math]::Round((Get-Item (Join-Path $dist "havline-$version.fpk")).Length / 1MB, 1) Write-Output "FPK 大小:$sizeMB MB" } finally { Pop-Location }