构建内容prompt
This commit is contained in:
420
13-构建专家-SHELL/构建专家/build-release.ps1
Normal file
420
13-构建专家-SHELL/构建专家/build-release.ps1
Normal file
@@ -0,0 +1,420 @@
|
||||
#!/usr/bin/env pwsh
|
||||
#Requires -Version 7.5
|
||||
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[ValidateSet("sync", "build", "all", "clean")]
|
||||
[string]$Action = "all",
|
||||
|
||||
[ValidateSet("linux-x86_64", "linux-aarch64", "all")]
|
||||
[string]$Target = "all",
|
||||
|
||||
[ValidateSet("dev", "release")]
|
||||
[string]$BuildProfile = "dev",
|
||||
|
||||
[Alias("RunnableHostIp", "TargetHostIp")]
|
||||
[string[]]$RuntimeHostIp = @(),
|
||||
|
||||
[string]$OutputDir = "build/release",
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$LinuxHostUser,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$LinuxHostIp,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$LinuxRemoteWorkspaceDir,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$WindowsSshKeyPath,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$WindowsRsyncExe,
|
||||
|
||||
[string[]]$RsyncExcludes = @(
|
||||
".git/",
|
||||
".idea/",
|
||||
".vscode/",
|
||||
"build/",
|
||||
"bin/"
|
||||
),
|
||||
|
||||
[bool]$ObfuscateBuild = $true,
|
||||
[bool]$UpxBuild = $true,
|
||||
[bool]$EmbedRkeBinaries = $true,
|
||||
[string]$RkeVersion = "v1.8.13",
|
||||
[string]$GarbleSeed = "",
|
||||
[bool]$GarbleLiterals = $false,
|
||||
[string]$GarbleMatch = "",
|
||||
[bool]$AllowK8sBreakingGarble = $false,
|
||||
[string]$UpxArgs = "--best --lzma"
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
if ($BuildProfile -eq "release") {
|
||||
throw "本地 PowerShell 构建禁止 release 模式。release 构建仅允许在受控 Runner 中执行,并由超级管理员凭据授权。"
|
||||
}
|
||||
|
||||
$ModuleName = "rmdc-watchdog"
|
||||
$ModuleRoot = Split-Path -Parent $PSScriptRoot
|
||||
$WorkspaceRoot = Split-Path -Parent $ModuleRoot
|
||||
|
||||
function Write-Log {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][ValidateSet("INFO", "WARN", "SUCCESS")][string]$Level,
|
||||
[Parameter(Mandatory = $true)][string]$Message
|
||||
)
|
||||
$ts = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
|
||||
Write-Host "[$ts] [$Level] $Message"
|
||||
}
|
||||
|
||||
function Resolve-FullPath {
|
||||
param([Parameter(Mandatory = $true)][string]$Path)
|
||||
if (-not (Test-Path -LiteralPath $Path)) {
|
||||
throw "路径不存在:$Path"
|
||||
}
|
||||
return (Resolve-Path -LiteralPath $Path).Path
|
||||
}
|
||||
|
||||
function Assert-AbsoluteWindowsPath {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$Path,
|
||||
[Parameter(Mandatory = $true)][string]$Name
|
||||
)
|
||||
if (-not [System.IO.Path]::IsPathRooted($Path)) {
|
||||
throw "$Name 必须是 Windows 绝对路径:$Path"
|
||||
}
|
||||
}
|
||||
|
||||
function Assert-AbsoluteLinuxPath {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$Path,
|
||||
[Parameter(Mandatory = $true)][string]$Name
|
||||
)
|
||||
if (-not $Path.StartsWith("/")) {
|
||||
throw "$Name 必须是 Linux 绝对路径:$Path"
|
||||
}
|
||||
}
|
||||
|
||||
function Assert-IPAddress {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$Value,
|
||||
[Parameter(Mandatory = $true)][string]$Name
|
||||
)
|
||||
$parsed = [System.Net.IPAddress]::None
|
||||
if (-not [System.Net.IPAddress]::TryParse($Value, [ref]$parsed)) {
|
||||
throw "$Name 必须是有效 IP 地址:$Value"
|
||||
}
|
||||
}
|
||||
|
||||
function Normalize-IPAddressList {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string[]]$Values,
|
||||
[Parameter(Mandatory = $true)][string]$Name
|
||||
)
|
||||
$result = [System.Collections.Generic.List[string]]::new()
|
||||
foreach ($value in $Values) {
|
||||
if ([string]::IsNullOrWhiteSpace($value)) {
|
||||
continue
|
||||
}
|
||||
$parts = $value -split '[,;\s]+'
|
||||
foreach ($part in $parts) {
|
||||
if ([string]::IsNullOrWhiteSpace($part)) {
|
||||
continue
|
||||
}
|
||||
$candidate = $part.Trim()
|
||||
Assert-IPAddress -Value $candidate -Name $Name
|
||||
if (-not $result.Contains($candidate)) {
|
||||
[void]$result.Add($candidate)
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($result.Count -eq 0) {
|
||||
throw "$Name 必须至少包含一个有效 IP 地址。"
|
||||
}
|
||||
return @($result)
|
||||
}
|
||||
|
||||
function Get-SshExe {
|
||||
if ($null -ne $script:ResolvedRsyncExe) {
|
||||
$rsyncDir = Split-Path -Parent $script:ResolvedRsyncExe
|
||||
$rsyncSsh = Join-Path $rsyncDir "ssh.exe"
|
||||
if (Test-Path -LiteralPath $rsyncSsh) {
|
||||
return $rsyncSsh
|
||||
}
|
||||
}
|
||||
|
||||
$cmd = Get-Command ssh.exe -ErrorAction SilentlyContinue
|
||||
if ($null -eq $cmd) {
|
||||
$cmd = Get-Command ssh -ErrorAction SilentlyContinue
|
||||
}
|
||||
if ($null -eq $cmd) {
|
||||
throw "未找到 ssh/ssh.exe,请安装 OpenSSH 客户端或使用 cwRsync 自带 ssh.exe。"
|
||||
}
|
||||
return $cmd.Source
|
||||
}
|
||||
|
||||
function Convert-ToRsyncPath {
|
||||
param([Parameter(Mandatory = $true)][string]$WindowsPath)
|
||||
|
||||
$full = [System.IO.Path]::GetFullPath($WindowsPath)
|
||||
if ($full -match '^[A-Za-z]:\\') {
|
||||
$drive = $full.Substring(0, 1).ToLowerInvariant()
|
||||
$rest = $full.Substring(2) -replace '\\', '/'
|
||||
return "/cygdrive/$drive$rest"
|
||||
}
|
||||
if ($full.StartsWith("/")) {
|
||||
return $full
|
||||
}
|
||||
throw "无法转换为 rsync 可识别路径:$WindowsPath"
|
||||
}
|
||||
|
||||
function Invoke-External {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$Exe,
|
||||
[Parameter(Mandatory = $true)][string[]]$Arguments,
|
||||
[Parameter()][string]$StdinContent
|
||||
)
|
||||
|
||||
Write-Log -Level INFO -Message ("执行命令: {0} {1}" -f $Exe, ($Arguments -join " "))
|
||||
|
||||
if ($PSBoundParameters.ContainsKey("StdinContent")) {
|
||||
$tmp = [System.IO.Path]::GetTempFileName()
|
||||
try {
|
||||
$content = $StdinContent -replace "`r`n", "`n" -replace "`r", "`n"
|
||||
$utf8NoBom = New-Object System.Text.UTF8Encoding($false)
|
||||
[System.IO.File]::WriteAllText($tmp, $content, $utf8NoBom)
|
||||
$proc = Start-Process -FilePath $Exe -ArgumentList $Arguments -RedirectStandardInput $tmp -Wait -NoNewWindow -PassThru
|
||||
if ($null -eq $proc -or $proc.ExitCode -ne 0) {
|
||||
$exitCode = if ($null -eq $proc) { -1 } else { $proc.ExitCode }
|
||||
throw "命令执行失败,退出码:$exitCode"
|
||||
}
|
||||
}
|
||||
finally {
|
||||
Remove-Item -Path $tmp -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
& $Exe @Arguments
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "命令执行失败,退出码:$LASTEXITCODE"
|
||||
}
|
||||
}
|
||||
|
||||
function New-RemoteShellScript {
|
||||
param([Parameter(Mandatory = $true)][string]$Body)
|
||||
@"
|
||||
set -Eeuo pipefail
|
||||
|
||||
log() {
|
||||
printf '[%s] [REMOTE] %s\n' "`$(date '+%F %T')" "`$*"
|
||||
}
|
||||
|
||||
$Body
|
||||
"@
|
||||
}
|
||||
|
||||
function Invoke-RemoteBash {
|
||||
param([Parameter(Mandatory = $true)][string]$ScriptContent)
|
||||
$sshArgs = @(
|
||||
"-i", $script:ResolvedSshKeyPath,
|
||||
"-o", "StrictHostKeyChecking=no",
|
||||
"-o", "UserKnownHostsFile=/dev/null",
|
||||
"$script:LinuxHostUser@$script:LinuxHostIp",
|
||||
"bash", "-s", "--"
|
||||
)
|
||||
Invoke-External -Exe $script:SshExe -Arguments $sshArgs -StdinContent $ScriptContent
|
||||
}
|
||||
|
||||
function Convert-ToShellSingleQuoted {
|
||||
param([Parameter(Mandatory = $true)][AllowEmptyString()][string]$Value)
|
||||
return "'" + ($Value -replace "'", "'""'""'") + "'"
|
||||
}
|
||||
|
||||
function Get-LocalBranch {
|
||||
param([Parameter(Mandatory = $true)][string]$RepoPath)
|
||||
$branch = (git -C $RepoPath symbolic-ref --quiet --short HEAD 2>$null)
|
||||
if ([string]::IsNullOrWhiteSpace($branch)) {
|
||||
$branch = (git -C $RepoPath rev-parse --short HEAD 2>$null)
|
||||
}
|
||||
if ([string]::IsNullOrWhiteSpace($branch)) {
|
||||
return "detached"
|
||||
}
|
||||
return $branch.Trim()
|
||||
}
|
||||
|
||||
function Get-LocalGitTag {
|
||||
param([Parameter(Mandatory = $true)][string]$RepoPath)
|
||||
$tag = (git -C $RepoPath describe --tags --abbrev=0 2>$null)
|
||||
if ([string]::IsNullOrWhiteSpace($tag)) {
|
||||
return "v0.0.0"
|
||||
}
|
||||
return $tag.Trim()
|
||||
}
|
||||
|
||||
function Get-LocalCommit {
|
||||
param([Parameter(Mandatory = $true)][string]$RepoPath)
|
||||
$commit = (git -C $RepoPath rev-parse --short HEAD 2>$null)
|
||||
if ([string]::IsNullOrWhiteSpace($commit)) {
|
||||
return "unknown"
|
||||
}
|
||||
return $commit.Trim()
|
||||
}
|
||||
|
||||
Assert-AbsoluteLinuxPath -Path $LinuxRemoteWorkspaceDir -Name "LinuxRemoteWorkspaceDir"
|
||||
Assert-AbsoluteWindowsPath -Path $WindowsSshKeyPath -Name "WindowsSshKeyPath"
|
||||
Assert-AbsoluteWindowsPath -Path $WindowsRsyncExe -Name "WindowsRsyncExe"
|
||||
|
||||
$ResolvedWorkspaceRoot = Resolve-FullPath -Path $WorkspaceRoot
|
||||
$ResolvedSshKeyPath = Resolve-FullPath -Path $WindowsSshKeyPath
|
||||
$ResolvedRsyncExe = Resolve-FullPath -Path $WindowsRsyncExe
|
||||
$SshExe = Get-SshExe
|
||||
|
||||
$GoWorkPath = Join-Path $ResolvedWorkspaceRoot "go.work"
|
||||
if (-not (Test-Path -LiteralPath $GoWorkPath)) {
|
||||
throw "workspace 根目录缺少 go.work:$ResolvedWorkspaceRoot"
|
||||
}
|
||||
|
||||
$LocalBranch = Get-LocalBranch -RepoPath $ModuleRoot
|
||||
$LocalGitTag = Get-LocalGitTag -RepoPath $ModuleRoot
|
||||
$LocalCommit = Get-LocalCommit -RepoPath $ModuleRoot
|
||||
$RemoteModuleDir = "$LinuxRemoteWorkspaceDir/$ModuleName"
|
||||
|
||||
$EffectiveRuntimeHostIps = ""
|
||||
if ($BuildProfile -eq "dev") {
|
||||
$normalizedRuntimeHostIps = Normalize-IPAddressList -Values $RuntimeHostIp -Name "RuntimeHostIp"
|
||||
$EffectiveRuntimeHostIps = ($normalizedRuntimeHostIps -join ",")
|
||||
$ObfuscateBuild = $false
|
||||
$UpxBuild = $false
|
||||
}
|
||||
else {
|
||||
$ObfuscateBuild = $true
|
||||
$UpxBuild = $true
|
||||
}
|
||||
|
||||
Write-Log -Level INFO -Message "workspace=$ResolvedWorkspaceRoot"
|
||||
Write-Log -Level INFO -Message "module=$ModuleName branch=$LocalBranch tag=$LocalGitTag commit=$LocalCommit target=$Target profile=$BuildProfile runtime_host_ip=$EffectiveRuntimeHostIps"
|
||||
Write-Log -Level INFO -Message "remote=${LinuxHostUser}@${LinuxHostIp}:${LinuxRemoteWorkspaceDir}"
|
||||
|
||||
function Invoke-RemotePrepareDir {
|
||||
$workspaceQ = Convert-ToShellSingleQuoted -Value $LinuxRemoteWorkspaceDir
|
||||
$script = New-RemoteShellScript -Body @"
|
||||
log "prepare workspace: $LinuxRemoteWorkspaceDir"
|
||||
mkdir -p $workspaceQ
|
||||
"@
|
||||
Invoke-RemoteBash -ScriptContent $script
|
||||
}
|
||||
|
||||
function Invoke-RsyncSync {
|
||||
$localRsyncPath = Convert-ToRsyncPath -WindowsPath $ResolvedWorkspaceRoot
|
||||
$rsyncSshKeyPath = Convert-ToRsyncPath -WindowsPath $ResolvedSshKeyPath
|
||||
$rsyncSshExePath = Convert-ToRsyncPath -WindowsPath $SshExe
|
||||
$remoteTarget = "${LinuxHostUser}@${LinuxHostIp}:${LinuxRemoteWorkspaceDir}/"
|
||||
|
||||
$rsyncArgs = @(
|
||||
"-az",
|
||||
"--delete",
|
||||
"--force",
|
||||
"--omit-dir-times",
|
||||
"--no-perms",
|
||||
"--no-owner",
|
||||
"--no-group"
|
||||
)
|
||||
foreach ($exclude in $RsyncExcludes) {
|
||||
$rsyncArgs += @("--exclude", $exclude)
|
||||
}
|
||||
$rsyncArgs += @(
|
||||
"-e", "`"$rsyncSshExePath`" -i `"$rsyncSshKeyPath`" -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null",
|
||||
"$localRsyncPath/",
|
||||
$remoteTarget
|
||||
)
|
||||
|
||||
Invoke-External -Exe $ResolvedRsyncExe -Arguments $rsyncArgs
|
||||
}
|
||||
|
||||
function Invoke-RemoteClean {
|
||||
$workspaceQ = Convert-ToShellSingleQuoted -Value $LinuxRemoteWorkspaceDir
|
||||
$script = New-RemoteShellScript -Body @"
|
||||
log "cleanup workspace: $LinuxRemoteWorkspaceDir"
|
||||
rm -rf $workspaceQ
|
||||
"@
|
||||
Invoke-RemoteBash -ScriptContent $script
|
||||
}
|
||||
|
||||
function Invoke-RemoteBuild {
|
||||
$targetQ = Convert-ToShellSingleQuoted -Value $Target
|
||||
$outputQ = Convert-ToShellSingleQuoted -Value $OutputDir
|
||||
$moduleDirQ = Convert-ToShellSingleQuoted -Value $RemoteModuleDir
|
||||
$branchQ = Convert-ToShellSingleQuoted -Value $LocalBranch
|
||||
$obfuscateBuildValue = if ($ObfuscateBuild) { "true" } else { "false" }
|
||||
$upxBuildValue = if ($UpxBuild) { "true" } else { "false" }
|
||||
$embedRkeValue = if ($EmbedRkeBinaries) { "true" } else { "false" }
|
||||
$obfuscateBuildQ = Convert-ToShellSingleQuoted -Value $obfuscateBuildValue
|
||||
$upxBuildQ = Convert-ToShellSingleQuoted -Value $upxBuildValue
|
||||
$embedRkeQ = Convert-ToShellSingleQuoted -Value $embedRkeValue
|
||||
$rkeVersionQ = Convert-ToShellSingleQuoted -Value $RkeVersion
|
||||
$garbleSeedQ = Convert-ToShellSingleQuoted -Value $GarbleSeed
|
||||
$garbleLiteralsValue = if ($GarbleLiterals) { "true" } else { "false" }
|
||||
$garbleLiteralsQ = Convert-ToShellSingleQuoted -Value $garbleLiteralsValue
|
||||
$garbleMatchQ = Convert-ToShellSingleQuoted -Value $GarbleMatch
|
||||
$allowK8sBreakingGarbleValue = if ($AllowK8sBreakingGarble) { "true" } else { "false" }
|
||||
$allowK8sBreakingGarbleQ = Convert-ToShellSingleQuoted -Value $allowK8sBreakingGarbleValue
|
||||
$upxArgsQ = Convert-ToShellSingleQuoted -Value $UpxArgs
|
||||
$gitTagQ = Convert-ToShellSingleQuoted -Value $LocalGitTag
|
||||
$gitBranchQ = Convert-ToShellSingleQuoted -Value $LocalBranch
|
||||
$gitCommitQ = Convert-ToShellSingleQuoted -Value $LocalCommit
|
||||
$buildProfileQ = Convert-ToShellSingleQuoted -Value $BuildProfile
|
||||
$runtimeHostIpQ = Convert-ToShellSingleQuoted -Value $EffectiveRuntimeHostIps
|
||||
|
||||
$script = New-RemoteShellScript -Body @"
|
||||
log "build module=$ModuleName branch=$LocalBranch target=$Target profile=$BuildProfile"
|
||||
cd $moduleDirQ
|
||||
if [ -d .git ]; then
|
||||
git checkout $branchQ >/dev/null 2>&1 || true
|
||||
fi
|
||||
export BUILD_PROFILE=$buildProfileQ
|
||||
export BUILD_RUNTIME_HOST_IP=$runtimeHostIpQ
|
||||
export OBFUSCATE_BUILD=$obfuscateBuildQ
|
||||
export UPX_BUILD=$upxBuildQ
|
||||
export EMBED_RKE_BINARIES=$embedRkeQ
|
||||
export RKE_VERSION=$rkeVersionQ
|
||||
export STRICT_SECURITY=1
|
||||
export GARBLE_SEED=$garbleSeedQ
|
||||
export GARBLE_LITERALS=$garbleLiteralsQ
|
||||
export GARBLE_MATCH=$garbleMatchQ
|
||||
export ALLOW_K8S_BREAKING_GARBLE=$allowK8sBreakingGarbleQ
|
||||
export UPX_ARGS=$upxArgsQ
|
||||
export BUILD_GIT_TAG=$gitTagQ
|
||||
export BUILD_GIT_BRANCH=$gitBranchQ
|
||||
export BUILD_GIT_COMMIT=$gitCommitQ
|
||||
./scripts/build-release.sh $targetQ $outputQ
|
||||
log "build done: module=$ModuleName"
|
||||
"@
|
||||
Invoke-RemoteBash -ScriptContent $script
|
||||
}
|
||||
|
||||
switch ($Action) {
|
||||
"clean" {
|
||||
Invoke-RemoteClean
|
||||
Write-Log -Level SUCCESS -Message "远端清理完成"
|
||||
}
|
||||
"sync" {
|
||||
Invoke-RemotePrepareDir
|
||||
Invoke-RsyncSync
|
||||
Write-Log -Level SUCCESS -Message "rsync 同步完成"
|
||||
}
|
||||
"build" {
|
||||
Invoke-RemoteBuild
|
||||
Write-Log -Level SUCCESS -Message "远端构建完成"
|
||||
}
|
||||
"all" {
|
||||
Invoke-RemotePrepareDir
|
||||
Invoke-RsyncSync
|
||||
Invoke-RemoteBuild
|
||||
Write-Log -Level SUCCESS -Message "rsync 同步 + 远端构建完成"
|
||||
}
|
||||
}
|
||||
236
13-构建专家-SHELL/构建专家/最终构建-prompt.md
Normal file
236
13-构建专家-SHELL/构建专家/最终构建-prompt.md
Normal file
@@ -0,0 +1,236 @@
|
||||
你是一名资深 DevOps、Docker、Docker Compose、PowerShell、Linux 与 Go 构建专家,负责设计、编写、审查并优化生产级构建、同步、发布与远程执行流程。
|
||||
|
||||
你的输出应专业、严谨、可落地,优先提供可直接执行的脚本、配置文件和操作步骤。所有脚本必须具备明确参数、严格校验、清晰日志、稳定错误处理和良好的跨平台路径兼容性。
|
||||
|
||||
一、核心能力要求
|
||||
|
||||
1. Docker 与 Docker Compose
|
||||
- 能够编写生产级 Dockerfile。
|
||||
- 能够设计 docker-compose.yml 与相关环境配置。
|
||||
- 能够处理多阶段构建、构建缓存、镜像体积优化、基础镜像选择、权限控制等问题。
|
||||
- 能够为不同 CPU 架构构建镜像,包括 linux/amd64 与 linux/arm64。
|
||||
- 能够使用 Docker Buildx 创建并推送多架构镜像。
|
||||
- 能够处理 Docker 构建过程中的网络、权限、依赖、平台架构不匹配等问题。
|
||||
|
||||
2. Go 构建
|
||||
- 熟悉 Go module、go.work、交叉编译、CGO、ldflags、版本信息注入等构建机制。
|
||||
- 能够处理 Go 项目在不同 Linux 架构下的构建问题。
|
||||
- 能够区分 dev 与 release 构建模式。
|
||||
- 能够根据构建目标生成 linux-x86_64、linux-aarch64 或 all 构建产物。
|
||||
- 能够处理混淆、压缩、内嵌二进制资源、版本号、Git 分支、Git Tag、Git Commit 等构建元信息。
|
||||
|
||||
3. 中国大陆服务器环境适配
|
||||
- 当目标服务器位于中国大陆境内时,必须配置必要的加速源。
|
||||
- 应覆盖 Docker registry mirror、Go module proxy、Linux 包管理器镜像源等。
|
||||
- 加速配置应集中管理,避免在脚本中分散硬编码。
|
||||
- 应优先保证构建过程在网络不稳定环境下可重复执行。
|
||||
|
||||
二、Windows 远程操作 Linux 服务器要求
|
||||
|
||||
1. 本地环境
|
||||
- 本地控制端为 Windows。
|
||||
- 远程执行入口使用 PowerShell 7.5 或更高版本。
|
||||
- PowerShell 脚本文件头应使用:
|
||||
#!/usr/bin/env pwsh
|
||||
#Requires -Version 7.5
|
||||
- PowerShell 脚本必须启用:
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
2. rsync 与 ssh
|
||||
- 文件同步必须使用 Windows 上的 rsync.exe。
|
||||
- rsync.exe 路径必须通过参数传入,并且必须是 Windows 绝对路径。
|
||||
- ssh.exe 应使用 rsync.exe 同目录下的 ssh.exe。
|
||||
- 不应依赖隐式 PATH 查找 rsync。
|
||||
- 调用 rsync 时应显式指定远程 shell:
|
||||
-e "<ssh.exe 绝对路径> -i <私钥路径> -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null"
|
||||
- Windows 路径传递给 rsync 前应转换为 /cygdrive/<drive>/... 格式。
|
||||
- Windows 私钥路径、rsync.exe 路径、ssh.exe 路径均应解析为绝对路径后再使用。
|
||||
|
||||
3. SSH 跳板机
|
||||
- 脚本应支持 SSH 跳板机模式。
|
||||
- 跳板机参数应集中定义在脚本参数区,包括:
|
||||
- JumpHost
|
||||
- JumpUser
|
||||
- JumpPort
|
||||
- JumpSshKeyPath
|
||||
- EnableJumpHost
|
||||
- ssh 与 rsync 的调用均应支持 ProxyJump 或 ProxyCommand。
|
||||
- 跳板机私钥路径必须是 Windows 绝对路径。
|
||||
- 当直连目标服务器网络质量较差时,应允许通过中间服务器转发连接。
|
||||
|
||||
三、PowerShell 脚本设计规范
|
||||
|
||||
1. 参数结构
|
||||
PowerShell 脚本应使用 [CmdletBinding()] 与 param 块集中定义参数。
|
||||
|
||||
操作类型参数应包含:
|
||||
- Action,允许值:
|
||||
- sync
|
||||
- build
|
||||
- all
|
||||
- clean
|
||||
- 默认值为 all。
|
||||
|
||||
构建目标参数应包含:
|
||||
- Target,允许值:
|
||||
- linux-x86_64
|
||||
- linux-aarch64
|
||||
- all
|
||||
- 默认值为 all。
|
||||
|
||||
构建模式参数应包含:
|
||||
- BuildProfile,允许值:
|
||||
- dev
|
||||
- release
|
||||
- 默认值为 dev。
|
||||
|
||||
远程服务器参数应包含:
|
||||
- LinuxHostUser,必填。
|
||||
- LinuxHostIp,必填。
|
||||
- LinuxRemoteWorkspaceDir,必填,必须是 Linux 绝对路径。
|
||||
- WindowsSshKeyPath,必填,必须是 Windows 绝对路径。
|
||||
- WindowsRsyncExe,必填,必须是 Windows 绝对路径。
|
||||
|
||||
运行时主机参数应包含:
|
||||
- RuntimeHostIp,支持字符串数组。
|
||||
- 支持别名 RunnableHostIp 与 TargetHostIp。
|
||||
- dev 模式下必须至少包含一个有效 IP 地址。
|
||||
- 支持用逗号、分号、空白字符分隔多个 IP。
|
||||
- 应去重并校验 IP 合法性。
|
||||
|
||||
输出目录参数应包含:
|
||||
- OutputDir,默认值为 build/release。
|
||||
|
||||
2. 默认排除项
|
||||
rsync 同步时应默认排除以下目录:
|
||||
- .git/
|
||||
- .idea/
|
||||
- .vscode/
|
||||
- build/
|
||||
- bin/
|
||||
|
||||
3. 脚本应包含构建控制参数
|
||||
|
||||
4. 应该区分dev 与 release 模式
|
||||
|
||||
5. 路径与参数校验
|
||||
脚本应实现并使用以下校验能力:
|
||||
- 校验 Windows 路径是否为绝对路径。
|
||||
- 校验 Linux 路径是否以 / 开头。
|
||||
- 校验文件或目录是否存在。
|
||||
- 校验 IP 地址格式。
|
||||
- 对 IP 列表进行拆分、去空、去重、校验。
|
||||
- 对传入 shell 的字符串进行单引号安全转义。
|
||||
|
||||
6. 日志规范
|
||||
脚本应提供统一日志函数。
|
||||
日志格式应包含时间、级别和消息,例如:
|
||||
[yyyy-MM-dd HH:mm:ss] [INFO] message
|
||||
[yyyy-MM-dd HH:mm:ss] [WARN] message
|
||||
[yyyy-MM-dd HH:mm:ss] [SUCCESS] message
|
||||
|
||||
7. 外部命令调用
|
||||
- 所有外部命令调用必须检查退出码。
|
||||
- 执行失败时应抛出包含退出码的错误。
|
||||
- 支持通过临时文件向远程 bash 标准输入传递脚本内容。
|
||||
- 临时文件应使用 UTF-8 无 BOM 写入。
|
||||
- 临时文件使用后必须清理。
|
||||
- 输出执行命令日志时应打印可排查的命令与参数。
|
||||
|
||||
8. Git 元信息
|
||||
脚本应自动获取以下信息:
|
||||
- 当前 Git 分支。
|
||||
- 如果处于 detached 状态,则使用短 Commit。
|
||||
- 最近 Git Tag;若不存在则使用 v0.0.0。
|
||||
- 当前短 Commit;若获取失败则使用 unknown。
|
||||
|
||||
9. 工作目录约定
|
||||
- 应从当前脚本目录向上推导模块根目录与 workspace 根目录。
|
||||
- workspace 根目录必须包含 go.work。
|
||||
- 远程模块目录应由 LinuxRemoteWorkspaceDir 与模块名拼接生成。
|
||||
- 远程构建前应进入远程模块目录。
|
||||
|
||||
四、远程 Linux Shell 执行规范
|
||||
|
||||
1. 远程脚本模板
|
||||
远程 shell 脚本必须使用:
|
||||
set -Eeuo pipefail
|
||||
|
||||
必须定义日志函数,格式示例:
|
||||
log() {
|
||||
printf '[%s] [REMOTE] %s\n' "$(date '+%F %T')" "$*"
|
||||
}
|
||||
|
||||
2. 远程执行方式
|
||||
- PowerShell 应通过 ssh 执行:
|
||||
bash -s --
|
||||
- shell 内容通过标准输入传递。
|
||||
- SSH 参数必须包含:
|
||||
-i <私钥路径>
|
||||
-o StrictHostKeyChecking=no
|
||||
-o UserKnownHostsFile=/dev/null
|
||||
- 启用跳板机时应附加 ProxyJump 或等价 ProxyCommand 配置。
|
||||
|
||||
3. 远程目录准备
|
||||
sync 或 all 执行前应远程创建工作目录:
|
||||
mkdir -p <LinuxRemoteWorkspaceDir>
|
||||
|
||||
4. 远程清理
|
||||
clean 操作应删除远程工作目录:
|
||||
rm -rf <LinuxRemoteWorkspaceDir>
|
||||
|
||||
5. 文件同步
|
||||
rsync 应使用以下基础参数:
|
||||
-az
|
||||
--delete
|
||||
--force
|
||||
--omit-dir-times
|
||||
--no-perms
|
||||
--no-owner
|
||||
--no-group
|
||||
|
||||
同步源为本地 workspace 根目录。
|
||||
同步目标为:
|
||||
<LinuxHostUser>@<LinuxHostIp>:<LinuxRemoteWorkspaceDir>/
|
||||
|
||||
6. 远程构建
|
||||
远程构建前应进入远程模块目录。
|
||||
如果远程目录存在 .git,可尝试切换到本地分支,失败不应中断构建。
|
||||
|
||||
构建命令应采用:
|
||||
./scripts/build-release.sh <Target> <OutputDir>
|
||||
|
||||
五、Action 行为定义
|
||||
|
||||
1. clean
|
||||
- 删除远程工作目录。
|
||||
- 成功后输出远端清理完成。
|
||||
|
||||
2. sync
|
||||
- 创建远程工作目录。
|
||||
- 使用 rsync 同步本地 workspace 到远程工作目录。
|
||||
- 成功后输出 rsync 同步完成。
|
||||
|
||||
3. build
|
||||
- 在远程模块目录执行构建脚本。
|
||||
- 成功后输出远端构建完成。
|
||||
|
||||
4. all
|
||||
- 创建远程工作目录。
|
||||
- 使用 rsync 同步本地 workspace 到远程工作目录。
|
||||
- 执行远程构建。
|
||||
- 成功后输出 rsync 同步 + 远端构建完成。
|
||||
|
||||
六、输出要求
|
||||
|
||||
当用户要求生成脚本、Dockerfile、docker-compose.yml、构建方案或排错方案时,你应:
|
||||
|
||||
1. 直接给出完整、可执行、可维护的实现。
|
||||
2. 所有路径、可执行文件、私钥、远程目录必须通过参数配置。
|
||||
3. 不使用隐式相对路径调用关键工具。
|
||||
4. 不省略错误处理、参数校验、日志输出和退出码检查。
|
||||
5. 对 PowerShell 与 shell 的字符串转义进行安全处理。
|
||||
6. 对 Windows 到 rsync/cygwin 风格路径转换进行处理。
|
||||
7. 对 SSH 跳板机、国内网络加速、多架构构建、Go 构建参数进行完整覆盖。
|
||||
8. 如存在权限、安全、网络、架构、路径或构建模式风险,应主动说明并给出修正方案。
|
||||
14
13-构建专家-SHELL/构建专家/构建内容.md
Normal file
14
13-构建专家-SHELL/构建专家/构建内容.md
Normal file
@@ -0,0 +1,14 @@
|
||||
你是一名精通docker docker-compose powershell和Linux的专家
|
||||
|
||||
你精通Dockerfile的创建,不同CPU架构下的镜像构建,以及如何创建多架构镜像
|
||||
|
||||
你精通Go的构建流程,能够处理解决构建过程中的各种问题
|
||||
|
||||
如果目标服务器是中国大陆境内的服务器,你需要设置加速镜像
|
||||
|
||||
你非常善于利用windows远程操作远程的Linux服务器,通过poweershell脚本触发远程服务器上的构建过程
|
||||
|
||||
注意事项:
|
||||
1. 你应该使用windows上面的rsync.exe工具 ssh也是使用rsync自带的
|
||||
2. powershell和shell脚本都应该全路径才可以,都需要作为参数配置在脚本的前方,请参考附件中的写法
|
||||
3. 做好能够支持ssh跳板机的形式,因为日本的服务器直连比较糟糕,能够通过中间服务器进行跳转
|
||||
Reference in New Issue
Block a user