deploy-copy.ps1 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
  1. #Requires -Version 5.1
  2. param(
  3. [switch] $NonInteractive,
  4. [ValidateSet('Sit', 'Uat', 'Prod')]
  5. [string] $Environment = 'Sit',
  6. [string] $GitUrl = 'http://8.152.195.41:3000/dujian/h5.git',
  7. [string] $Branch = 'master',
  8. [string] $SshUser = 'root',
  9. # 留空则使用当前环境在 $Environments 中的默认远程目录
  10. [string] $RemotePath = '',
  11. # 非空时覆盖环境默认 IP(便于 Jenkins / 命令行临时指定)
  12. [string] $TargetHost = ''
  13. )
  14. <#
  15. .SYNOPSIS
  16. 从 Git 将 master(或指定分支)部署到目标服务器:先清空远程目录再覆盖上传。
  17. .DESCRIPTION
  18. 无参数启动:图形界面。加 -NonInteractive 时供 Jenkins 等无人值守调用。
  19. 默认仓库:http://8.152.195.41:3000/dujian/h5.git
  20. 默认:Sit → 120.26.186.130:/docker/middleware/nginx/html/h5/;
  21. Uat → 39.106.135.88:/alien_uat/nginx/html/h5/;
  22. Prod → 39.106.135.88:/alien_produ/nginx/html/h5/
  23. #>
  24. $ErrorActionPreference = 'Stop'
  25. $Environments = @(
  26. @{
  27. Key = 'Sit'
  28. Label = 'Sit(测试环境)'
  29. Server = '120.26.186.130'
  30. RemotePath = '/docker/middleware/nginx/html/h5/'
  31. },
  32. @{
  33. Key = 'Uat'
  34. Label = 'Uat(预生产环境)'
  35. Server = '39.106.135.88'
  36. RemotePath = '/alien_uat/nginx/html/h5/'
  37. },
  38. @{
  39. Key = 'Prod'
  40. Label = 'Prod(生产环境)'
  41. Server = '39.106.135.88'
  42. RemotePath = '/alien_produ/nginx/html/h5/'
  43. }
  44. )
  45. function Test-GitAvailable {
  46. $g = Get-Command git -ErrorAction SilentlyContinue
  47. if (-not $g) {
  48. throw "未找到 git。请先安装 Git for Windows 并确保 git 在 PATH 中。"
  49. }
  50. }
  51. function Test-SshScpAvailable {
  52. $ssh = Get-Command ssh -ErrorAction SilentlyContinue
  53. $scp = Get-Command scp -ErrorAction SilentlyContinue
  54. if (-not $ssh -or -not $scp) {
  55. throw "未找到 ssh 或 scp。请在 Windows「可选功能」中安装「OpenSSH 客户端」。"
  56. }
  57. }
  58. function Invoke-GitCloneMaster {
  59. param(
  60. [string] $RepoUrl,
  61. [string] $Branch,
  62. [string] $CloneInto
  63. )
  64. if (Test-Path -LiteralPath $CloneInto) {
  65. Remove-Item -LiteralPath $CloneInto -Recurse -Force
  66. }
  67. New-Item -ItemType Directory -Path (Split-Path -LiteralPath $CloneInto -Parent) -Force | Out-Null
  68. $cloneArgs = @('clone', '--depth', '1', '--branch', $Branch, $RepoUrl, $CloneInto)
  69. & git @cloneArgs
  70. if ($LASTEXITCODE -ne 0) {
  71. throw "git clone 失败,退出码:$LASTEXITCODE"
  72. }
  73. }
  74. function Remove-LocalGitMetadata {
  75. param([string] $RepoRoot)
  76. $gitDir = Join-Path $RepoRoot '.git'
  77. if (Test-Path -LiteralPath $gitDir) {
  78. Remove-Item -LiteralPath $gitDir -Recurse -Force
  79. }
  80. }
  81. function Invoke-RemoteClearDirectory {
  82. param(
  83. [string] $User,
  84. [string] $TargetHost,
  85. [string] $RemotePath
  86. )
  87. $r = $RemotePath.Trim().TrimEnd('/')
  88. if ($r.IndexOf([char]39) -ge 0 -or $r.Contains('$') -or $r.Contains('`')) {
  89. throw '远程目录路径暂不支持包含单引号、反引号或美元符号。'
  90. }
  91. $remoteShell = "mkdir -p '$r' && find '$r' -mindepth 1 -delete"
  92. & ssh @('-o', 'StrictHostKeyChecking=accept-new', "${User}@${TargetHost}", $remoteShell)
  93. if ($LASTEXITCODE -ne 0) {
  94. throw "远程清空目录失败(ssh),退出码:$LASTEXITCODE"
  95. }
  96. }
  97. function Invoke-DeployScp {
  98. param(
  99. [string] $StagePath,
  100. [string] $User,
  101. [string] $TargetHost,
  102. [string] $RemotePath
  103. )
  104. $dest = ('{0}@{1}:{2}' -f $User, $TargetHost, ($RemotePath.TrimEnd('/') + '/'))
  105. $items = @(Get-ChildItem -LiteralPath $StagePath -Force)
  106. if ($items.Count -eq 0) {
  107. throw '克隆后的目录为空,无法上传。'
  108. }
  109. $scpArgs = @('-r', '-o', 'StrictHostKeyChecking=accept-new') + ($items | ForEach-Object { $_.FullName }) + @($dest)
  110. & scp @scpArgs
  111. if ($LASTEXITCODE -ne 0) {
  112. throw "scp 失败,退出码:$LASTEXITCODE"
  113. }
  114. }
  115. function Invoke-H5DeployFromGit {
  116. param(
  117. [Parameter(Mandatory)]
  118. [ValidateSet('Sit', 'Uat', 'Prod')]
  119. [string] $Environment,
  120. [string] $RepoUrl,
  121. [string] $Branch,
  122. [string] $User,
  123. [string] $RemotePath,
  124. [string] $TargetHostOverride
  125. )
  126. $envItem = @($Environments | Where-Object { $_.Key -eq $Environment })[0]
  127. if (-not $envItem) { throw "未知环境:$Environment" }
  128. $hostAddr = if ($TargetHostOverride) { $TargetHostOverride.Trim() } else { $envItem.Server }
  129. if (-not $hostAddr) { throw '目标主机不能为空。' }
  130. $resolvedRemote = if ($RemotePath -and $RemotePath.Trim()) { $RemotePath.Trim() } else { $envItem.RemotePath }
  131. if (-not $resolvedRemote) { throw '远程目录不能为空。' }
  132. Test-GitAvailable
  133. Test-SshScpAvailable
  134. $base = Join-Path ([System.IO.Path]::GetTempPath()) ('h5-deploy-' + [Guid]::NewGuid().ToString('N'))
  135. $cloneInto = Join-Path $base 'h5'
  136. try {
  137. Write-Host "[$($envItem.Key)] 克隆 $Branch …"
  138. Invoke-GitCloneMaster -RepoUrl $RepoUrl -Branch $Branch -CloneInto $cloneInto
  139. Write-Host '移除 .git(仅上传站点文件)…'
  140. Remove-LocalGitMetadata -RepoRoot $cloneInto
  141. Write-Host "清空远程目录 $hostAddr`:$resolvedRemote …"
  142. Invoke-RemoteClearDirectory -User $User -TargetHost $hostAddr -RemotePath $resolvedRemote
  143. Write-Host "上传到 $hostAddr …"
  144. Invoke-DeployScp -StagePath $cloneInto -User $User -TargetHost $hostAddr -RemotePath $resolvedRemote
  145. Write-Host "完成:$($envItem.Label) → ${User}@${hostAddr}:${resolvedRemote}"
  146. }
  147. finally {
  148. if (Test-Path -LiteralPath $base) {
  149. Remove-Item -LiteralPath $base -Recurse -Force -ErrorAction SilentlyContinue
  150. }
  151. }
  152. }
  153. if ($NonInteractive) {
  154. try {
  155. Invoke-H5DeployFromGit -Environment $Environment -RepoUrl $GitUrl -Branch $Branch `
  156. -User $SshUser -RemotePath $RemotePath -TargetHostOverride $TargetHost
  157. exit 0
  158. }
  159. catch {
  160. Write-Error $_
  161. exit 1
  162. }
  163. }
  164. # ---------- 以下为图形界面 ----------
  165. Add-Type -AssemblyName System.Windows.Forms
  166. Add-Type -AssemblyName System.Drawing
  167. $DefaultGitUrl = $GitUrl
  168. $DefaultBranch = $Branch
  169. $form = New-Object System.Windows.Forms.Form
  170. $form.Text = '从 Git 部署 H5 到服务器'
  171. $form.Size = New-Object System.Drawing.Size(520, 400)
  172. $form.StartPosition = 'CenterScreen'
  173. $form.FormBorderStyle = 'FixedDialog'
  174. $form.MaximizeBox = $false
  175. $y = 18
  176. $dy = 36
  177. $lblEnv = New-Object System.Windows.Forms.Label
  178. $lblEnv.Location = New-Object System.Drawing.Point(20, $y)
  179. $lblEnv.Size = New-Object System.Drawing.Size(120, 22)
  180. $lblEnv.Text = '环境:'
  181. $combo = New-Object System.Windows.Forms.ComboBox
  182. $combo.Location = New-Object System.Drawing.Point(140, ($y - 2))
  183. $combo.Size = New-Object System.Drawing.Size(340, 24)
  184. $combo.DropDownStyle = 'DropDownList'
  185. foreach ($envDef in $Environments) {
  186. [void] $combo.Items.Add($envDef.Label)
  187. }
  188. $idxGui = 0
  189. for ($i = 0; $i -lt $Environments.Count; $i++) {
  190. if ($Environments[$i].Key -eq $Environment) { $idxGui = $i; break }
  191. }
  192. $combo.SelectedIndex = $idxGui
  193. $y += $dy
  194. $lblGit = New-Object System.Windows.Forms.Label
  195. $lblGit.Location = New-Object System.Drawing.Point(20, $y)
  196. $lblGit.Size = New-Object System.Drawing.Size(120, 22)
  197. $lblGit.Text = '仓库 URL:'
  198. $txtGit = New-Object System.Windows.Forms.TextBox
  199. $txtGit.Location = New-Object System.Drawing.Point(140, ($y - 2))
  200. $txtGit.Size = New-Object System.Drawing.Size(340, 22)
  201. $txtGit.Text = $DefaultGitUrl
  202. $y += $dy
  203. $lblBranch = New-Object System.Windows.Forms.Label
  204. $lblBranch.Location = New-Object System.Drawing.Point(20, $y)
  205. $lblBranch.Size = New-Object System.Drawing.Size(120, 22)
  206. $lblBranch.Text = '分支:'
  207. $txtBranch = New-Object System.Windows.Forms.TextBox
  208. $txtBranch.Location = New-Object System.Drawing.Point(140, ($y - 2))
  209. $txtBranch.Size = New-Object System.Drawing.Size(120, 22)
  210. $txtBranch.Text = $DefaultBranch
  211. $y += $dy
  212. $lblHost = New-Object System.Windows.Forms.Label
  213. $lblHost.Location = New-Object System.Drawing.Point(20, $y)
  214. $lblHost.Size = New-Object System.Drawing.Size(120, 22)
  215. $lblHost.Text = '目标主机:'
  216. $txtHost = New-Object System.Windows.Forms.TextBox
  217. $txtHost.Location = New-Object System.Drawing.Point(140, ($y - 2))
  218. $txtHost.Size = New-Object System.Drawing.Size(340, 22)
  219. $txtHost.ReadOnly = $true
  220. $y += $dy
  221. $lblUser = New-Object System.Windows.Forms.Label
  222. $lblUser.Location = New-Object System.Drawing.Point(20, $y)
  223. $lblUser.Size = New-Object System.Drawing.Size(120, 22)
  224. $lblUser.Text = 'SSH 用户:'
  225. $txtUser = New-Object System.Windows.Forms.TextBox
  226. $txtUser.Location = New-Object System.Drawing.Point(140, ($y - 2))
  227. $txtUser.Size = New-Object System.Drawing.Size(340, 22)
  228. $txtUser.Text = $SshUser
  229. $y += $dy
  230. $lblPath = New-Object System.Windows.Forms.Label
  231. $lblPath.Location = New-Object System.Drawing.Point(20, $y)
  232. $lblPath.Size = New-Object System.Drawing.Size(120, 22)
  233. $lblPath.Text = '远程目录:'
  234. $txtPath = New-Object System.Windows.Forms.TextBox
  235. $txtPath.Location = New-Object System.Drawing.Point(140, ($y - 2))
  236. $txtPath.Size = New-Object System.Drawing.Size(340, 22)
  237. $sel0 = $Environments[$idxGui]
  238. $txtHost.Text = $sel0.Server
  239. $txtPath.Text = if ($RemotePath.Trim()) { $RemotePath.Trim() } else { $sel0.RemotePath }
  240. $y += 44
  241. $btnOk = New-Object System.Windows.Forms.Button
  242. $btnOk.Location = New-Object System.Drawing.Point(160, $y)
  243. $btnOk.Size = New-Object System.Drawing.Size(120, 32)
  244. $btnOk.Text = '开始部署'
  245. $btnCancel = New-Object System.Windows.Forms.Button
  246. $btnCancel.Location = New-Object System.Drawing.Point(300, $y)
  247. $btnCancel.Size = New-Object System.Drawing.Size(120, 32)
  248. $btnCancel.Text = '取消'
  249. $y += 48
  250. $status = New-Object System.Windows.Forms.Label
  251. $status.Location = New-Object System.Drawing.Point(20, $y)
  252. $status.Size = New-Object System.Drawing.Size(470, 72)
  253. $status.Text = "将克隆:$DefaultGitUrl ($DefaultBranch)`n上传前会删除远程目录内已有文件后再覆盖。"
  254. function Update-FromEnvironmentSelection {
  255. $idx = $combo.SelectedIndex
  256. if ($idx -lt 0) { return }
  257. $sel = $Environments[$idx]
  258. $txtHost.Text = $sel.Server
  259. $txtPath.Text = $sel.RemotePath
  260. }
  261. $combo.Add_SelectedIndexChanged({ Update-FromEnvironmentSelection })
  262. $form.Controls.AddRange(@(
  263. $lblEnv, $combo,
  264. $lblGit, $txtGit,
  265. $lblBranch, $txtBranch,
  266. $lblHost, $txtHost,
  267. $lblUser, $txtUser,
  268. $lblPath, $txtPath,
  269. $btnOk, $btnCancel,
  270. $status
  271. ))
  272. $btnCancel.Add_Click({ $form.DialogResult = 'Cancel'; $form.Close() })
  273. $btnOk.Add_Click({
  274. try {
  275. $idx = $combo.SelectedIndex
  276. if ($idx -lt 0) { throw '请选择环境。' }
  277. $envItem = $Environments[$idx]
  278. $hostAddr = $txtHost.Text.Trim()
  279. $user = $txtUser.Text.Trim()
  280. $remotePath = $txtPath.Text.Trim()
  281. $repoUrl = $txtGit.Text.Trim()
  282. $branch = $txtBranch.Text.Trim()
  283. if (-not $repoUrl) { throw '仓库 URL 不能为空。' }
  284. if (-not $branch) { throw '分支不能为空。' }
  285. $btnOk.Enabled = $false
  286. $btnCancel.Enabled = $false
  287. Invoke-H5DeployFromGit -Environment $envItem.Key -RepoUrl $repoUrl -Branch $branch `
  288. -User $user -RemotePath $remotePath -TargetHostOverride ''
  289. [System.Windows.Forms.MessageBox]::Show(
  290. ("部署完成。`n环境:{0}`n仓库:{1} ({2})`n目标:{3}@{4}:{5}" -f `
  291. $envItem.Label, $repoUrl, $branch, $user, $hostAddr, $remotePath),
  292. '成功',
  293. 'OK',
  294. 'Information'
  295. ) | Out-Null
  296. $form.DialogResult = 'OK'
  297. $form.Close()
  298. }
  299. catch {
  300. [System.Windows.Forms.MessageBox]::Show(
  301. $_.Exception.Message,
  302. '错误',
  303. 'OK',
  304. 'Error'
  305. ) | Out-Null
  306. $btnOk.Enabled = $true
  307. $btnCancel.Enabled = $true
  308. $status.Text = "将克隆:$DefaultGitUrl ($DefaultBranch)`n上传前会删除远程目录内已有文件后再覆盖。"
  309. }
  310. })
  311. [void] $form.ShowDialog()