|
|
@@ -0,0 +1,357 @@
|
|
|
+#Requires -Version 5.1
|
|
|
+param(
|
|
|
+ [switch] $NonInteractive,
|
|
|
+ [ValidateSet('Sit', 'Uat', 'Prod')]
|
|
|
+ [string] $Environment = 'Sit',
|
|
|
+ [string] $GitUrl = 'http://8.152.195.41:3000/dujian/h5.git',
|
|
|
+ [string] $Branch = 'master',
|
|
|
+ [string] $SshUser = 'root',
|
|
|
+ # 留空则使用当前环境在 $Environments 中的默认远程目录
|
|
|
+ [string] $RemotePath = '',
|
|
|
+ # 非空时覆盖环境默认 IP(便于 Jenkins / 命令行临时指定)
|
|
|
+ [string] $TargetHost = ''
|
|
|
+)
|
|
|
+
|
|
|
+<#
|
|
|
+.SYNOPSIS
|
|
|
+ 从 Git 将 master(或指定分支)部署到目标服务器:先清空远程目录再覆盖上传。
|
|
|
+
|
|
|
+.DESCRIPTION
|
|
|
+ 无参数启动:图形界面。加 -NonInteractive 时供 Jenkins 等无人值守调用。
|
|
|
+ 默认仓库:http://8.152.195.41:3000/dujian/h5.git
|
|
|
+ 默认:Sit → 120.26.186.130:/docker/middleware/nginx/html/h5/;
|
|
|
+ Uat → 39.106.135.88:/alien_uat/nginx/html/h5/;
|
|
|
+ Prod → 39.106.135.88:/alien_produ/nginx/html/h5/
|
|
|
+#>
|
|
|
+
|
|
|
+$ErrorActionPreference = 'Stop'
|
|
|
+
|
|
|
+$Environments = @(
|
|
|
+ @{
|
|
|
+ Key = 'Sit'
|
|
|
+ Label = 'Sit(测试环境)'
|
|
|
+ Server = '120.26.186.130'
|
|
|
+ RemotePath = '/docker/middleware/nginx/html/h5/'
|
|
|
+ },
|
|
|
+ @{
|
|
|
+ Key = 'Uat'
|
|
|
+ Label = 'Uat(预生产环境)'
|
|
|
+ Server = '39.106.135.88'
|
|
|
+ RemotePath = '/alien_uat/nginx/html/h5/'
|
|
|
+ },
|
|
|
+ @{
|
|
|
+ Key = 'Prod'
|
|
|
+ Label = 'Prod(生产环境)'
|
|
|
+ Server = '39.106.135.88'
|
|
|
+ RemotePath = '/alien_produ/nginx/html/h5/'
|
|
|
+ }
|
|
|
+)
|
|
|
+
|
|
|
+function Test-GitAvailable {
|
|
|
+ $g = Get-Command git -ErrorAction SilentlyContinue
|
|
|
+ if (-not $g) {
|
|
|
+ throw "未找到 git。请先安装 Git for Windows 并确保 git 在 PATH 中。"
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function Test-SshScpAvailable {
|
|
|
+ $ssh = Get-Command ssh -ErrorAction SilentlyContinue
|
|
|
+ $scp = Get-Command scp -ErrorAction SilentlyContinue
|
|
|
+ if (-not $ssh -or -not $scp) {
|
|
|
+ throw "未找到 ssh 或 scp。请在 Windows「可选功能」中安装「OpenSSH 客户端」。"
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function Invoke-GitCloneMaster {
|
|
|
+ param(
|
|
|
+ [string] $RepoUrl,
|
|
|
+ [string] $Branch,
|
|
|
+ [string] $CloneInto
|
|
|
+ )
|
|
|
+ if (Test-Path -LiteralPath $CloneInto) {
|
|
|
+ Remove-Item -LiteralPath $CloneInto -Recurse -Force
|
|
|
+ }
|
|
|
+ New-Item -ItemType Directory -Path (Split-Path -LiteralPath $CloneInto -Parent) -Force | Out-Null
|
|
|
+ $cloneArgs = @('clone', '--depth', '1', '--branch', $Branch, $RepoUrl, $CloneInto)
|
|
|
+ & git @cloneArgs
|
|
|
+ if ($LASTEXITCODE -ne 0) {
|
|
|
+ throw "git clone 失败,退出码:$LASTEXITCODE"
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function Remove-LocalGitMetadata {
|
|
|
+ param([string] $RepoRoot)
|
|
|
+ $gitDir = Join-Path $RepoRoot '.git'
|
|
|
+ if (Test-Path -LiteralPath $gitDir) {
|
|
|
+ Remove-Item -LiteralPath $gitDir -Recurse -Force
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function Invoke-RemoteClearDirectory {
|
|
|
+ param(
|
|
|
+ [string] $User,
|
|
|
+ [string] $TargetHost,
|
|
|
+ [string] $RemotePath
|
|
|
+ )
|
|
|
+ $r = $RemotePath.Trim().TrimEnd('/')
|
|
|
+ if ($r.IndexOf([char]39) -ge 0 -or $r.Contains('$') -or $r.Contains('`')) {
|
|
|
+ throw '远程目录路径暂不支持包含单引号、反引号或美元符号。'
|
|
|
+ }
|
|
|
+ $remoteShell = "mkdir -p '$r' && find '$r' -mindepth 1 -delete"
|
|
|
+ & ssh @('-o', 'StrictHostKeyChecking=accept-new', "${User}@${TargetHost}", $remoteShell)
|
|
|
+ if ($LASTEXITCODE -ne 0) {
|
|
|
+ throw "远程清空目录失败(ssh),退出码:$LASTEXITCODE"
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function Invoke-DeployScp {
|
|
|
+ param(
|
|
|
+ [string] $StagePath,
|
|
|
+ [string] $User,
|
|
|
+ [string] $TargetHost,
|
|
|
+ [string] $RemotePath
|
|
|
+ )
|
|
|
+ $dest = ('{0}@{1}:{2}' -f $User, $TargetHost, ($RemotePath.TrimEnd('/') + '/'))
|
|
|
+ $items = @(Get-ChildItem -LiteralPath $StagePath -Force)
|
|
|
+ if ($items.Count -eq 0) {
|
|
|
+ throw '克隆后的目录为空,无法上传。'
|
|
|
+ }
|
|
|
+ $scpArgs = @('-r', '-o', 'StrictHostKeyChecking=accept-new') + ($items | ForEach-Object { $_.FullName }) + @($dest)
|
|
|
+ & scp @scpArgs
|
|
|
+ if ($LASTEXITCODE -ne 0) {
|
|
|
+ throw "scp 失败,退出码:$LASTEXITCODE"
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function Invoke-H5DeployFromGit {
|
|
|
+ param(
|
|
|
+ [Parameter(Mandatory)]
|
|
|
+ [ValidateSet('Sit', 'Uat', 'Prod')]
|
|
|
+ [string] $Environment,
|
|
|
+ [string] $RepoUrl,
|
|
|
+ [string] $Branch,
|
|
|
+ [string] $User,
|
|
|
+ [string] $RemotePath,
|
|
|
+ [string] $TargetHostOverride
|
|
|
+ )
|
|
|
+ $envItem = @($Environments | Where-Object { $_.Key -eq $Environment })[0]
|
|
|
+ if (-not $envItem) { throw "未知环境:$Environment" }
|
|
|
+ $hostAddr = if ($TargetHostOverride) { $TargetHostOverride.Trim() } else { $envItem.Server }
|
|
|
+ if (-not $hostAddr) { throw '目标主机不能为空。' }
|
|
|
+ $resolvedRemote = if ($RemotePath -and $RemotePath.Trim()) { $RemotePath.Trim() } else { $envItem.RemotePath }
|
|
|
+ if (-not $resolvedRemote) { throw '远程目录不能为空。' }
|
|
|
+
|
|
|
+ Test-GitAvailable
|
|
|
+ Test-SshScpAvailable
|
|
|
+
|
|
|
+ $base = Join-Path ([System.IO.Path]::GetTempPath()) ('h5-deploy-' + [Guid]::NewGuid().ToString('N'))
|
|
|
+ $cloneInto = Join-Path $base 'h5'
|
|
|
+ try {
|
|
|
+ Write-Host "[$($envItem.Key)] 克隆 $Branch …"
|
|
|
+ Invoke-GitCloneMaster -RepoUrl $RepoUrl -Branch $Branch -CloneInto $cloneInto
|
|
|
+
|
|
|
+ Write-Host '移除 .git(仅上传站点文件)…'
|
|
|
+ Remove-LocalGitMetadata -RepoRoot $cloneInto
|
|
|
+
|
|
|
+ Write-Host "清空远程目录 $hostAddr`:$resolvedRemote …"
|
|
|
+ Invoke-RemoteClearDirectory -User $User -TargetHost $hostAddr -RemotePath $resolvedRemote
|
|
|
+
|
|
|
+ Write-Host "上传到 $hostAddr …"
|
|
|
+ Invoke-DeployScp -StagePath $cloneInto -User $User -TargetHost $hostAddr -RemotePath $resolvedRemote
|
|
|
+
|
|
|
+ Write-Host "完成:$($envItem.Label) → ${User}@${hostAddr}:${resolvedRemote}"
|
|
|
+ }
|
|
|
+ finally {
|
|
|
+ if (Test-Path -LiteralPath $base) {
|
|
|
+ Remove-Item -LiteralPath $base -Recurse -Force -ErrorAction SilentlyContinue
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+if ($NonInteractive) {
|
|
|
+ try {
|
|
|
+ Invoke-H5DeployFromGit -Environment $Environment -RepoUrl $GitUrl -Branch $Branch `
|
|
|
+ -User $SshUser -RemotePath $RemotePath -TargetHostOverride $TargetHost
|
|
|
+ exit 0
|
|
|
+ }
|
|
|
+ catch {
|
|
|
+ Write-Error $_
|
|
|
+ exit 1
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+# ---------- 以下为图形界面 ----------
|
|
|
+Add-Type -AssemblyName System.Windows.Forms
|
|
|
+Add-Type -AssemblyName System.Drawing
|
|
|
+
|
|
|
+$DefaultGitUrl = $GitUrl
|
|
|
+$DefaultBranch = $Branch
|
|
|
+
|
|
|
+$form = New-Object System.Windows.Forms.Form
|
|
|
+$form.Text = '从 Git 部署 H5 到服务器'
|
|
|
+$form.Size = New-Object System.Drawing.Size(520, 400)
|
|
|
+$form.StartPosition = 'CenterScreen'
|
|
|
+$form.FormBorderStyle = 'FixedDialog'
|
|
|
+$form.MaximizeBox = $false
|
|
|
+
|
|
|
+$y = 18
|
|
|
+$dy = 36
|
|
|
+
|
|
|
+$lblEnv = New-Object System.Windows.Forms.Label
|
|
|
+$lblEnv.Location = New-Object System.Drawing.Point(20, $y)
|
|
|
+$lblEnv.Size = New-Object System.Drawing.Size(120, 22)
|
|
|
+$lblEnv.Text = '环境:'
|
|
|
+
|
|
|
+$combo = New-Object System.Windows.Forms.ComboBox
|
|
|
+$combo.Location = New-Object System.Drawing.Point(140, ($y - 2))
|
|
|
+$combo.Size = New-Object System.Drawing.Size(340, 24)
|
|
|
+$combo.DropDownStyle = 'DropDownList'
|
|
|
+foreach ($envDef in $Environments) {
|
|
|
+ [void] $combo.Items.Add($envDef.Label)
|
|
|
+}
|
|
|
+$idxGui = 0
|
|
|
+for ($i = 0; $i -lt $Environments.Count; $i++) {
|
|
|
+ if ($Environments[$i].Key -eq $Environment) { $idxGui = $i; break }
|
|
|
+}
|
|
|
+$combo.SelectedIndex = $idxGui
|
|
|
+$y += $dy
|
|
|
+
|
|
|
+$lblGit = New-Object System.Windows.Forms.Label
|
|
|
+$lblGit.Location = New-Object System.Drawing.Point(20, $y)
|
|
|
+$lblGit.Size = New-Object System.Drawing.Size(120, 22)
|
|
|
+$lblGit.Text = '仓库 URL:'
|
|
|
+
|
|
|
+$txtGit = New-Object System.Windows.Forms.TextBox
|
|
|
+$txtGit.Location = New-Object System.Drawing.Point(140, ($y - 2))
|
|
|
+$txtGit.Size = New-Object System.Drawing.Size(340, 22)
|
|
|
+$txtGit.Text = $DefaultGitUrl
|
|
|
+$y += $dy
|
|
|
+
|
|
|
+$lblBranch = New-Object System.Windows.Forms.Label
|
|
|
+$lblBranch.Location = New-Object System.Drawing.Point(20, $y)
|
|
|
+$lblBranch.Size = New-Object System.Drawing.Size(120, 22)
|
|
|
+$lblBranch.Text = '分支:'
|
|
|
+
|
|
|
+$txtBranch = New-Object System.Windows.Forms.TextBox
|
|
|
+$txtBranch.Location = New-Object System.Drawing.Point(140, ($y - 2))
|
|
|
+$txtBranch.Size = New-Object System.Drawing.Size(120, 22)
|
|
|
+$txtBranch.Text = $DefaultBranch
|
|
|
+$y += $dy
|
|
|
+
|
|
|
+$lblHost = New-Object System.Windows.Forms.Label
|
|
|
+$lblHost.Location = New-Object System.Drawing.Point(20, $y)
|
|
|
+$lblHost.Size = New-Object System.Drawing.Size(120, 22)
|
|
|
+$lblHost.Text = '目标主机:'
|
|
|
+
|
|
|
+$txtHost = New-Object System.Windows.Forms.TextBox
|
|
|
+$txtHost.Location = New-Object System.Drawing.Point(140, ($y - 2))
|
|
|
+$txtHost.Size = New-Object System.Drawing.Size(340, 22)
|
|
|
+$txtHost.ReadOnly = $true
|
|
|
+$y += $dy
|
|
|
+
|
|
|
+$lblUser = New-Object System.Windows.Forms.Label
|
|
|
+$lblUser.Location = New-Object System.Drawing.Point(20, $y)
|
|
|
+$lblUser.Size = New-Object System.Drawing.Size(120, 22)
|
|
|
+$lblUser.Text = 'SSH 用户:'
|
|
|
+
|
|
|
+$txtUser = New-Object System.Windows.Forms.TextBox
|
|
|
+$txtUser.Location = New-Object System.Drawing.Point(140, ($y - 2))
|
|
|
+$txtUser.Size = New-Object System.Drawing.Size(340, 22)
|
|
|
+$txtUser.Text = $SshUser
|
|
|
+$y += $dy
|
|
|
+
|
|
|
+$lblPath = New-Object System.Windows.Forms.Label
|
|
|
+$lblPath.Location = New-Object System.Drawing.Point(20, $y)
|
|
|
+$lblPath.Size = New-Object System.Drawing.Size(120, 22)
|
|
|
+$lblPath.Text = '远程目录:'
|
|
|
+
|
|
|
+$txtPath = New-Object System.Windows.Forms.TextBox
|
|
|
+$txtPath.Location = New-Object System.Drawing.Point(140, ($y - 2))
|
|
|
+$txtPath.Size = New-Object System.Drawing.Size(340, 22)
|
|
|
+$sel0 = $Environments[$idxGui]
|
|
|
+$txtHost.Text = $sel0.Server
|
|
|
+$txtPath.Text = if ($RemotePath.Trim()) { $RemotePath.Trim() } else { $sel0.RemotePath }
|
|
|
+$y += 44
|
|
|
+
|
|
|
+$btnOk = New-Object System.Windows.Forms.Button
|
|
|
+$btnOk.Location = New-Object System.Drawing.Point(160, $y)
|
|
|
+$btnOk.Size = New-Object System.Drawing.Size(120, 32)
|
|
|
+$btnOk.Text = '开始部署'
|
|
|
+
|
|
|
+$btnCancel = New-Object System.Windows.Forms.Button
|
|
|
+$btnCancel.Location = New-Object System.Drawing.Point(300, $y)
|
|
|
+$btnCancel.Size = New-Object System.Drawing.Size(120, 32)
|
|
|
+$btnCancel.Text = '取消'
|
|
|
+$y += 48
|
|
|
+
|
|
|
+$status = New-Object System.Windows.Forms.Label
|
|
|
+$status.Location = New-Object System.Drawing.Point(20, $y)
|
|
|
+$status.Size = New-Object System.Drawing.Size(470, 72)
|
|
|
+$status.Text = "将克隆:$DefaultGitUrl ($DefaultBranch)`n上传前会删除远程目录内已有文件后再覆盖。"
|
|
|
+
|
|
|
+function Update-FromEnvironmentSelection {
|
|
|
+ $idx = $combo.SelectedIndex
|
|
|
+ if ($idx -lt 0) { return }
|
|
|
+ $sel = $Environments[$idx]
|
|
|
+ $txtHost.Text = $sel.Server
|
|
|
+ $txtPath.Text = $sel.RemotePath
|
|
|
+}
|
|
|
+
|
|
|
+$combo.Add_SelectedIndexChanged({ Update-FromEnvironmentSelection })
|
|
|
+
|
|
|
+$form.Controls.AddRange(@(
|
|
|
+ $lblEnv, $combo,
|
|
|
+ $lblGit, $txtGit,
|
|
|
+ $lblBranch, $txtBranch,
|
|
|
+ $lblHost, $txtHost,
|
|
|
+ $lblUser, $txtUser,
|
|
|
+ $lblPath, $txtPath,
|
|
|
+ $btnOk, $btnCancel,
|
|
|
+ $status
|
|
|
+ ))
|
|
|
+
|
|
|
+$btnCancel.Add_Click({ $form.DialogResult = 'Cancel'; $form.Close() })
|
|
|
+
|
|
|
+$btnOk.Add_Click({
|
|
|
+ try {
|
|
|
+ $idx = $combo.SelectedIndex
|
|
|
+ if ($idx -lt 0) { throw '请选择环境。' }
|
|
|
+ $envItem = $Environments[$idx]
|
|
|
+ $hostAddr = $txtHost.Text.Trim()
|
|
|
+ $user = $txtUser.Text.Trim()
|
|
|
+ $remotePath = $txtPath.Text.Trim()
|
|
|
+ $repoUrl = $txtGit.Text.Trim()
|
|
|
+ $branch = $txtBranch.Text.Trim()
|
|
|
+ if (-not $repoUrl) { throw '仓库 URL 不能为空。' }
|
|
|
+ if (-not $branch) { throw '分支不能为空。' }
|
|
|
+
|
|
|
+ $btnOk.Enabled = $false
|
|
|
+ $btnCancel.Enabled = $false
|
|
|
+
|
|
|
+ Invoke-H5DeployFromGit -Environment $envItem.Key -RepoUrl $repoUrl -Branch $branch `
|
|
|
+ -User $user -RemotePath $remotePath -TargetHostOverride ''
|
|
|
+
|
|
|
+ [System.Windows.Forms.MessageBox]::Show(
|
|
|
+ ("部署完成。`n环境:{0}`n仓库:{1} ({2})`n目标:{3}@{4}:{5}" -f `
|
|
|
+ $envItem.Label, $repoUrl, $branch, $user, $hostAddr, $remotePath),
|
|
|
+ '成功',
|
|
|
+ 'OK',
|
|
|
+ 'Information'
|
|
|
+ ) | Out-Null
|
|
|
+ $form.DialogResult = 'OK'
|
|
|
+ $form.Close()
|
|
|
+ }
|
|
|
+ catch {
|
|
|
+ [System.Windows.Forms.MessageBox]::Show(
|
|
|
+ $_.Exception.Message,
|
|
|
+ '错误',
|
|
|
+ 'OK',
|
|
|
+ 'Error'
|
|
|
+ ) | Out-Null
|
|
|
+ $btnOk.Enabled = $true
|
|
|
+ $btnCancel.Enabled = $true
|
|
|
+ $status.Text = "将克隆:$DefaultGitUrl ($DefaultBranch)`n上传前会删除远程目录内已有文件后再覆盖。"
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+[void] $form.ShowDialog()
|