param( [string]$DeployEnv = $(if ($env:DEPLOY_ENV) { $env:DEPLOY_ENV } else { "emp-test" }), [string]$ImageNamespace = $(if ($env:IMAGE_NAMESPACE) { $env:IMAGE_NAMESPACE } else { "emp-test" }), [string]$ImageTag = $(if ($env:IMAGE_TAG) { $env:IMAGE_TAG } else { Get-Date -Format "yyyyMMddHHmmss" }), [string]$NpmRegistry = $(if ($env:NPM_REGISTRY) { $env:NPM_REGISTRY } else { "https://registry.npmjs.org" }), [string]$EmpRoot = $(if ($env:EMP_ROOT) { $env:EMP_ROOT } else { "" }), [string]$ProfileDir = $(if ($env:PROFILE_DIR) { $env:PROFILE_DIR } else { "" }), [switch]$SkipBuild, [switch]$NoMiddlewareImages, [switch]$CosUpload ) $ErrorActionPreference = "Stop" # Local package script. Build images locally, export them, then run on server only with docker load + compose up. $ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path $RootDir = $null $DistDir = Join-Path $ScriptDir "dist" $BuildContextDir = Join-Path $ScriptDir ".build-context" if (-not $env:IMAGE_NAMESPACE -and $ImageNamespace -eq "emp-test" -and $DeployEnv -ne "emp-test") { $ImageNamespace = $DeployEnv } $PackageName = $(if ($env:PACKAGE_NAME) { $env:PACKAGE_NAME } else { "$DeployEnv-runtime-$ImageTag" }) $PackageDir = Join-Path $DistDir $PackageName $PackageArchive = Join-Path $DistDir "$PackageName.tar.gz" $JavaModules = @("emp_gateway", "emp_auth", "emp_monitor", "emp_data") $AppImages = @( "$ImageNamespace/emp-gateway:$ImageTag", "$ImageNamespace/emp-gateway:latest", "$ImageNamespace/emp-auth:$ImageTag", "$ImageNamespace/emp-auth:latest", "$ImageNamespace/emp-monitor:$ImageTag", "$ImageNamespace/emp-monitor:latest", "$ImageNamespace/emp-data:$ImageTag", "$ImageNamespace/emp-data:latest", "$ImageNamespace/emp-pdf:$ImageTag", "$ImageNamespace/emp-pdf:latest", "$ImageNamespace/emp-ws:$ImageTag", "$ImageNamespace/emp-ws:latest", "$ImageNamespace/emp-admin:$ImageTag", "$ImageNamespace/emp-admin:latest" ) $MiddlewareImages = @( $(if ($env:MYSQL_IMAGE) { $env:MYSQL_IMAGE } else { "mysql:8.0" }), $(if ($env:REDIS_IMAGE) { $env:REDIS_IMAGE } else { "redis:7-alpine" }), $(if ($env:KAFKA_IMAGE) { $env:KAFKA_IMAGE } else { "bitnami/kafka:3.7.0" }), $(if ($env:TDENGINE_IMAGE) { $env:TDENGINE_IMAGE } else { "tdengine/tdengine:3.3.6.0" }), $(if ($env:NACOS_IMAGE) { $env:NACOS_IMAGE } else { "nacos/nacos-server:v2.3.2-slim" }) ) function Write-Log { param([string]$Message) Write-Host "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] $Message" } function Add-KnownToolPaths { $KnownDirs = @( "C:\Program Files\Docker\Docker\resources\bin", "C:\Program Files\Docker\Docker", "$env:ProgramFiles\Docker\Docker\resources\bin" ) foreach ($Dir in $KnownDirs) { if ($Dir -and (Test-Path $Dir) -and (($env:Path -split ';') -notcontains $Dir)) { $env:Path = "$Dir;$env:Path" } } } function Assert-Command { param([string]$Name) if (-not (Get-Command $Name -ErrorAction SilentlyContinue)) { throw "Missing command: $Name" } } function Assert-DockerDaemon { & docker info *> $null if ($LASTEXITCODE -ne 0) { throw "Docker daemon is not running. Start Docker Desktop and wait until it is ready, then rerun this script." } } function Test-EmpRoot { param([string]$Path) return (Test-Path (Join-Path $Path "emp_server")) ` -and (Test-Path (Join-Path $Path "emp_admin")) ` -and (Test-Path (Join-Path $Path "emp_ws")) } function Resolve-EmpRoot { if ($EmpRoot) { $Resolved = (Resolve-Path $EmpRoot).Path if (-not (Test-EmpRoot $Resolved)) { throw "EMP_ROOT is invalid: $EmpRoot" } return $Resolved } $Candidates = @( (Join-Path $ScriptDir "..\.."), (Join-Path $ScriptDir "..\..\emp"), (Join-Path $ScriptDir ".."), (Get-Location).Path ) foreach ($Candidate in $Candidates) { if (Test-Path $Candidate) { $Resolved = (Resolve-Path $Candidate).Path if (Test-EmpRoot $Resolved) { return $Resolved } } } throw "Cannot find EMP root. Set EMP_ROOT=/path/to/emp where emp_server, emp_admin and emp_ws exist." } function Invoke-Step { param( [string]$WorkingDirectory, [string]$FilePath, [string[]]$Arguments ) Push-Location $WorkingDirectory try { & $FilePath @Arguments if ($LASTEXITCODE -ne 0) { throw "$FilePath failed, exit code: $LASTEXITCODE" } } finally { Pop-Location } } function Build-JavaImages { $ServerDir = Join-Path $RootDir "emp_server" Write-Log "Build backend jars" Invoke-Step $ServerDir "mvn" @("package", "-DskipTests", "-B", "-pl", ($JavaModules -join ","), "-am") foreach ($Module in $JavaModules) { $TargetDir = Join-Path $ServerDir "$Module\target" $Jar = Get-ChildItem $TargetDir -Filter "*.jar" | Where-Object { $_.Name -ne "app.jar" -and $_.Name -notlike "*original*" } | Select-Object -First 1 if (-not $Jar) { throw "Jar not found for $Module" } Copy-Item $Jar.FullName (Join-Path $TargetDir "app.jar") -Force } Write-Log "Build backend images" Invoke-Step $ServerDir "docker" @("build", "-f", "Dockerfile.service", "--build-arg", "MODULE=emp_gateway", "-t", "$ImageNamespace/emp-gateway:$ImageTag", "-t", "$ImageNamespace/emp-gateway:latest", ".") Invoke-Step $ServerDir "docker" @("build", "-f", "Dockerfile.service", "--build-arg", "MODULE=emp_auth", "-t", "$ImageNamespace/emp-auth:$ImageTag", "-t", "$ImageNamespace/emp-auth:latest", ".") Invoke-Step $ServerDir "docker" @("build", "-f", "Dockerfile.service", "--build-arg", "MODULE=emp_monitor", "-t", "$ImageNamespace/emp-monitor:$ImageTag", "-t", "$ImageNamespace/emp-monitor:latest", ".") Invoke-Step $ServerDir "docker" @("build", "-f", "Dockerfile.service", "--build-arg", "MODULE=emp_data", "-t", "$ImageNamespace/emp-data:$ImageTag", "-t", "$ImageNamespace/emp-data:latest", ".") Write-Log "Build PDF image" Invoke-Step $ServerDir "docker" @("build", "-f", "emp_pdf/Dockerfile", "-t", "$ImageNamespace/emp-pdf:$ImageTag", "-t", "$ImageNamespace/emp-pdf:latest", "emp_pdf") } function Build-WsImage { Write-Log "Build WS/simulator image" $Dockerfile = Join-Path $ScriptDir "dockerfiles\emp-ws.Dockerfile" Invoke-Step $RootDir "docker" @( "build", "-f", $Dockerfile, "--build-arg", "NPM_REGISTRY=$NpmRegistry", "-t", "$ImageNamespace/emp-ws:$ImageTag", "-t", "$ImageNamespace/emp-ws:latest", "." ) } function Build-AdminImage { $AdminDir = Join-Path $RootDir "emp_admin" Write-Log "Build frontend dist" Invoke-Step $AdminDir "pnpm" @("install", "--frozen-lockfile") Invoke-Step $AdminDir "pnpm" @("run", "build:shunfeng") Write-Log "Prepare frontend image context" $ContextDir = Join-Path $BuildContextDir "emp-admin" if (Test-Path $ContextDir) { Remove-Item $ContextDir -Recurse -Force } New-Item -ItemType Directory -Force $ContextDir | Out-Null Copy-Item (Join-Path $AdminDir "dist") (Join-Path $ContextDir "dist") -Recurse -Force Copy-Item (Join-Path $ScriptDir "nginx\admin.conf") (Join-Path $ContextDir "admin.conf") -Force Write-Log "Build frontend image" $Dockerfile = Join-Path $ScriptDir "dockerfiles\emp-admin.Dockerfile" Invoke-Step $ContextDir "docker" @( "build", "-f", $Dockerfile, "-t", "$ImageNamespace/emp-admin:$ImageTag", "-t", "$ImageNamespace/emp-admin:latest", "." ) } function Prepare-Package { if (Test-Path $PackageDir) { Remove-Item $PackageDir -Recurse -Force } New-Item -ItemType Directory -Force $PackageDir | Out-Null $ComposeSource = Join-Path $ScriptDir "docker-compose.runtime.yml" $EnvSource = Join-Path $ScriptDir ".env.example" $ResolvedProfileDir = $(if ($ProfileDir) { $ProfileDir } else { Join-Path $ScriptDir "profiles\$DeployEnv" }) $CommonProfileComposeSource = Join-Path $ScriptDir "profiles\docker-compose.yml" $ProfileComposeSource = Join-Path $ResolvedProfileDir "docker-compose.yml" $ProfileEnvSource = Join-Path $ResolvedProfileDir ".env.example" $ProfileEnvTxtSource = Join-Path $ResolvedProfileDir "env.txt" $TestComposeSource = Join-Path $ScriptDir "test\docker-compose.yml" $TestEnvSource = Join-Path $ScriptDir "test\env.txt" if (Test-Path $ProfileComposeSource) { $ComposeSource = $ProfileComposeSource } elseif (Test-Path $CommonProfileComposeSource) { $ComposeSource = $CommonProfileComposeSource } elseif ($DeployEnv -eq "emp-test" -and (Test-Path $TestComposeSource)) { $ComposeSource = $TestComposeSource } if (Test-Path $ProfileEnvSource) { $EnvSource = $ProfileEnvSource } elseif (Test-Path $ProfileEnvTxtSource) { $EnvSource = $ProfileEnvTxtSource } elseif ($DeployEnv -eq "emp-test" -and (Test-Path $TestEnvSource)) { $EnvSource = $TestEnvSource } Copy-Item $ComposeSource (Join-Path $PackageDir "docker-compose.yml") -Force Copy-Item $EnvSource (Join-Path $PackageDir ".env.example") -Force Copy-Item (Join-Path $ScriptDir "install.sh") (Join-Path $PackageDir "install.sh") -Force Copy-Item (Join-Path $ScriptDir "deploy-from-url.sh") (Join-Path $PackageDir "deploy-from-url.sh") -Force Copy-Item (Join-Path $ScriptDir "README.md") (Join-Path $PackageDir "README.md") -Force } function Save-Images { $Images = New-Object System.Collections.Generic.List[string] foreach ($Image in $AppImages) { $Images.Add($Image) } if (-not $NoMiddlewareImages) { Write-Log "Pull middleware images" foreach ($Image in $MiddlewareImages) { Invoke-Step $RootDir "docker" @("pull", $Image) $Images.Add($Image) } } Write-Log "Save images" Invoke-Step $RootDir "docker" @(@("save", "-o", (Join-Path $PackageDir "images.tar")) + $Images.ToArray()) } function Archive-Package { New-Item -ItemType Directory -Force $DistDir | Out-Null if (Test-Path $PackageArchive) { Remove-Item $PackageArchive -Force } Invoke-Step $DistDir "tar" @("-czf", $PackageArchive, $PackageName) Write-Log "Package created: $PackageArchive" } function Publish-Package { if (-not $CosUpload -and $env:COS_UPLOAD -ne "1") { return } $CoscliBin = $(if ($env:COSCLI_BIN) { $env:COSCLI_BIN } else { "coscli" }) Assert-Command $CoscliBin if (-not $env:COS_BUCKET) { throw "Missing COS_BUCKET" } $CoscliOptions = @() if ($env:COS_CONFIG_PATH) { $CoscliOptions += @("-c", $env:COS_CONFIG_PATH) } else { if (-not $env:COS_SECRET_ID) { throw "Missing COS_SECRET_ID or COS_CONFIG_PATH" } if (-not $env:COS_SECRET_KEY) { throw "Missing COS_SECRET_KEY or COS_CONFIG_PATH" } if (-not $env:COS_REGION) { throw "Missing COS_REGION" } $CoscliOptions += @( "--init-skip=true", "-i", $env:COS_SECRET_ID, "-k", $env:COS_SECRET_KEY, "-e", "cos.$($env:COS_REGION).myqcloud.com" ) if ($env:COS_TOKEN) { $CoscliOptions += @("--token", $env:COS_TOKEN) } } $RunId = Get-Date -Format "yyyyMMddHHmmss" $CosSignExpire = $(if ($env:COS_SIGN_EXPIRE) { $env:COS_SIGN_EXPIRE } else { "604800" }) $DefaultPrefix = "deploy/$DeployEnv/runtime/$RunId" $CosPrefix = $(if ($env:COS_PREFIX) { $env:COS_PREFIX } else { $DefaultPrefix }).Trim("/") $PackageBase = Split-Path -Leaf $PackageArchive $CosKey = $(if ($env:COS_KEY) { $env:COS_KEY } else { "$CosPrefix/$PackageBase" }).TrimStart("/") $CosUri = "cos://$($env:COS_BUCKET)/$CosKey" $Sha256 = (Get-FileHash $PackageArchive -Algorithm SHA256).Hash.ToLowerInvariant() Write-Log "Upload package to COS: $CosUri" & $CoscliBin @(@("cp", $PackageArchive, $CosUri) + $CoscliOptions) if ($LASTEXITCODE -ne 0) { throw "coscli cp failed, exit code: $LASTEXITCODE" } Write-Log "Generate signed URL, expire seconds: $CosSignExpire" $SignedOutput = & $CoscliBin @(@("signurl", $CosUri, "--time", $CosSignExpire, "--simple-output") + $CoscliOptions) if ($LASTEXITCODE -ne 0) { throw "coscli signurl failed, exit code: $LASTEXITCODE" } $SignedUrl = ($SignedOutput | Select-Object -Last 1).ToString() Write-Host "" Write-Host "Package: $PackageArchive" Write-Host "COS Key: $CosKey" Write-Host "SHA256: $Sha256" Write-Host "URL: $SignedUrl" Write-Host "" Write-Host "Target deploy command:" Write-Host " DEPLOY_ENV=$DeployEnv PACKAGE_SHA256=$Sha256 bash deploy-from-url.sh `"$SignedUrl`"" Write-Host "" } Add-KnownToolPaths Assert-Command "docker" Assert-DockerDaemon Assert-Command "tar" $RootDir = Resolve-EmpRoot Write-Log "EMP root: $RootDir" Write-Log "Deploy root: $ScriptDir" Write-Log "Deploy env: $DeployEnv" if (-not $SkipBuild) { Assert-Command "mvn" Assert-Command "pnpm" Build-JavaImages Build-WsImage Build-AdminImage } else { Write-Log "Skip build, package existing local images only" } Prepare-Package Save-Images Archive-Package Publish-Package