Consolidated ASP Classic MVC framework from best components
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

266 lignes
9.8KB

  1. <#
  2. .SYNOPSIS
  3. Packages this repository and deploys it to IIS over Tailscale/OpenSSH.
  4. .DESCRIPTION
  5. This is the controller/CI entry point. It creates a ZIP containing the full
  6. repository (excluding VCS and local deployment artifacts), computes SHA-256,
  7. copies the package and host installer with scp, then invokes the installer on
  8. the Windows host through ssh.
  9. -DryRun performs local validation and prints the remote operations without
  10. connecting. Gitea 1.11.4 does not run this itself; use a trusted external CI
  11. worker, a scheduled task, or an operator workstation.
  12. #>
  13. [CmdletBinding()]
  14. param(
  15. [Parameter(Mandatory = $true)]
  16. [ValidatePattern('^[A-Za-z0-9_. -]+$')]
  17. [string]$SiteName,
  18. [string]$RemoteTarget = 'webserver-1',
  19. [ValidateRange(1, 65535)]
  20. [int]$RemotePort = 22,
  21. [string]$SourcePath = (Split-Path $PSScriptRoot -Parent),
  22. [string]$DeployRoot = '',
  23. [string]$BaseUrl = '',
  24. [string]$ReleaseId = '',
  25. [string]$ExpectedBranch = 'master',
  26. [ValidateRange(2, 100)]
  27. [int]$KeepReleases = 5,
  28. [string]$SshExe = 'ssh',
  29. [string]$ScpExe = 'scp',
  30. [switch]$AllowAnyBranch,
  31. [switch]$AllowDirty,
  32. [switch]$RunMigrations,
  33. [switch]$SkipSmokeTest,
  34. [switch]$RemotePreflightOnly,
  35. [switch]$Rollback,
  36. [string]$RollbackTo = '',
  37. [switch]$DryRun,
  38. [switch]$KeepPackage
  39. )
  40. Set-StrictMode -Version 2.0
  41. $ErrorActionPreference = 'Stop'
  42. function Write-Step {
  43. param([string]$Message)
  44. Write-Host ('==> ' + $Message)
  45. }
  46. function Assert-Command {
  47. param([string]$Name)
  48. if (-not (Get-Command $Name -ErrorAction SilentlyContinue)) {
  49. throw "$Name was not found on PATH."
  50. }
  51. }
  52. function ConvertTo-SingleQuotedPowerShell {
  53. param([string]$Value)
  54. return "'" + $Value.Replace("'", "''") + "'"
  55. }
  56. function Add-RemoteArgument {
  57. param(
  58. [System.Collections.Generic.List[string]]$Arguments,
  59. [string]$Name,
  60. [string]$Value
  61. )
  62. $Arguments.Add($Name)
  63. $Arguments.Add((ConvertTo-SingleQuotedPowerShell $Value))
  64. }
  65. function Copy-ReleaseSource {
  66. param(
  67. [string]$From,
  68. [string]$To
  69. )
  70. New-Item -ItemType Directory -Force -Path $To | Out-Null
  71. $excludedNames = @('.git', '.deployment', 'releases')
  72. Get-ChildItem -LiteralPath $From -Force | ForEach-Object {
  73. if ($excludedNames -notcontains $_.Name) {
  74. Copy-Item -LiteralPath $_.FullName -Destination $To -Recurse -Force
  75. }
  76. }
  77. }
  78. $SourcePath = [System.IO.Path]::GetFullPath($SourcePath)
  79. $installerPath = Join-Path $PSScriptRoot 'install-iis-release.ps1'
  80. if (-not (Test-Path -LiteralPath $installerPath -PathType Leaf)) {
  81. throw "Host installer is missing: $installerPath"
  82. }
  83. $requiredSourceFiles = @(
  84. 'public\Default.asp',
  85. 'public\web.config',
  86. 'core\autoload_core.asp',
  87. 'app\controllers\autoload_controllers.asp'
  88. )
  89. foreach ($relativePath in $requiredSourceFiles) {
  90. if (-not (Test-Path -LiteralPath (Join-Path $SourcePath $relativePath) -PathType Leaf)) {
  91. throw "Source tree is incomplete; missing $relativePath"
  92. }
  93. }
  94. try {
  95. [xml](Get-Content -LiteralPath (Join-Path $SourcePath 'public\web.config') -Raw) | Out-Null
  96. } catch {
  97. throw "Source public\web.config is not valid XML: $($_.Exception.Message)"
  98. }
  99. $directGitRoot = Test-Path -LiteralPath (Join-Path $SourcePath '.git')
  100. $commit = 'nogit'
  101. if ($directGitRoot) {
  102. Assert-Command 'git'
  103. $branch = (& git -C $SourcePath branch --show-current).Trim()
  104. if ($LASTEXITCODE -ne 0) {
  105. throw 'Could not determine the Git branch.'
  106. }
  107. if ((-not $AllowAnyBranch) -and $branch -ne $ExpectedBranch) {
  108. throw "Refusing to deploy branch '$branch'; expected '$ExpectedBranch'. Use -AllowAnyBranch only for an intentional exception."
  109. }
  110. $dirty = & git -C $SourcePath status --porcelain
  111. if ($LASTEXITCODE -ne 0) {
  112. throw 'Could not inspect the Git worktree.'
  113. }
  114. if ((-not $AllowDirty) -and $null -ne $dirty -and @($dirty).Count -gt 0) {
  115. throw 'Refusing to deploy a dirty worktree. Commit/stash changes or use -AllowDirty for an intentional, auditable exception.'
  116. }
  117. $commit = (& git -C $SourcePath rev-parse --short=12 HEAD).Trim()
  118. if ($LASTEXITCODE -ne 0) {
  119. throw 'Could not determine the Git commit.'
  120. }
  121. Write-Host "Source branch: $branch"
  122. Write-Host "Source commit: $commit"
  123. } else {
  124. Write-Warning 'SourcePath is not a standalone Git checkout; branch and dirty-worktree checks cannot be enforced.'
  125. if (-not $AllowAnyBranch) {
  126. throw 'Use a standalone CI checkout, or pass -AllowAnyBranch explicitly for a reviewed non-Git source tree.'
  127. }
  128. }
  129. if ([string]::IsNullOrWhiteSpace($ReleaseId)) {
  130. $ReleaseId = (Get-Date -Format 'yyyyMMdd-HHmmss') + '-' + $commit
  131. }
  132. if ($ReleaseId -notmatch '^[A-Za-z0-9._-]+$') {
  133. throw 'ReleaseId may contain only letters, numbers, dot, underscore, and hyphen.'
  134. }
  135. if ($Rollback -and [string]::IsNullOrWhiteSpace($RollbackTo)) {
  136. throw '-Rollback requires -RollbackTo <release-id>.'
  137. }
  138. if ((-not $Rollback) -and -not [string]::IsNullOrWhiteSpace($RollbackTo)) {
  139. throw '-RollbackTo is only valid with -Rollback.'
  140. }
  141. if ($RunMigrations -and $Rollback) {
  142. throw '-RunMigrations is not valid during rollback.'
  143. }
  144. $workRoot = Join-Path ([System.IO.Path]::GetTempPath()) ('asp-iis-deploy-' + [Guid]::NewGuid().ToString('N'))
  145. $packageStage = Join-Path $workRoot 'package'
  146. $packagePath = Join-Path $workRoot ($ReleaseId + '.zip')
  147. $remoteDirectory = 'C:\Windows\Temp\asp-iis-deploy-' + $ReleaseId
  148. $remotePackage = $remoteDirectory + '\' + $ReleaseId + '.zip'
  149. $remoteInstaller = $remoteDirectory + '\install-iis-release.ps1'
  150. try {
  151. if (-not $Rollback -and -not $RemotePreflightOnly) {
  152. Write-Step 'Staging the full repository for packaging'
  153. Copy-ReleaseSource -From $SourcePath -To $packageStage
  154. Add-Type -AssemblyName System.IO.Compression.FileSystem
  155. [System.IO.Compression.ZipFile]::CreateFromDirectory(
  156. $packageStage,
  157. $packagePath,
  158. [System.IO.Compression.CompressionLevel]::Optimal,
  159. $false
  160. )
  161. $sha256 = (Get-FileHash -LiteralPath $packagePath -Algorithm SHA256).Hash
  162. Write-Host "Package: $packagePath"
  163. Write-Host "SHA-256: $sha256"
  164. } else {
  165. $sha256 = ''
  166. }
  167. $remoteArguments = New-Object 'System.Collections.Generic.List[string]'
  168. $remoteArguments.Add('&')
  169. $remoteArguments.Add((ConvertTo-SingleQuotedPowerShell $remoteInstaller))
  170. Add-RemoteArgument -Arguments $remoteArguments -Name '-SiteName' -Value $SiteName
  171. if ($Rollback) {
  172. Add-RemoteArgument -Arguments $remoteArguments -Name '-RollbackTo' -Value $RollbackTo
  173. } elseif (-not $RemotePreflightOnly) {
  174. Add-RemoteArgument -Arguments $remoteArguments -Name '-PackagePath' -Value $remotePackage
  175. Add-RemoteArgument -Arguments $remoteArguments -Name '-ReleaseId' -Value $ReleaseId
  176. Add-RemoteArgument -Arguments $remoteArguments -Name '-ExpectedSha256' -Value $sha256
  177. } else {
  178. $remoteArguments.Add('-PreflightOnly')
  179. }
  180. if (-not [string]::IsNullOrWhiteSpace($DeployRoot)) {
  181. Add-RemoteArgument -Arguments $remoteArguments -Name '-DeployRoot' -Value $DeployRoot
  182. }
  183. if (-not [string]::IsNullOrWhiteSpace($BaseUrl)) {
  184. Add-RemoteArgument -Arguments $remoteArguments -Name '-BaseUrl' -Value $BaseUrl
  185. }
  186. Add-RemoteArgument -Arguments $remoteArguments -Name '-KeepReleases' -Value $KeepReleases.ToString()
  187. if ($RunMigrations) { $remoteArguments.Add('-RunMigrations') }
  188. if ($SkipSmokeTest) { $remoteArguments.Add('-SkipSmokeTest') }
  189. $remoteScript = $remoteArguments -join ' '
  190. $remoteBytes = [Text.Encoding]::Unicode.GetBytes($remoteScript)
  191. $remoteEncodedCommand = [Convert]::ToBase64String($remoteBytes)
  192. $remoteCommand = 'powershell.exe -NoProfile -NonInteractive -ExecutionPolicy RemoteSigned -EncodedCommand ' + $remoteEncodedCommand
  193. Write-Host "Remote target: $RemoteTarget (Tailscale/OpenSSH port $RemotePort)"
  194. if ($DryRun) {
  195. Write-Step 'Dry-run complete; no network connection was made'
  196. Write-Host "Would create remote directory: $remoteDirectory"
  197. Write-Host "Would copy installer: $installerPath"
  198. if (-not $Rollback -and -not $RemotePreflightOnly) {
  199. Write-Host "Would copy package: $packagePath"
  200. }
  201. Write-Host ('Would execute host script: ' + $remoteScript)
  202. Write-Host ('Transport command uses PowerShell -EncodedCommand to avoid remote-shell quoting ambiguity.')
  203. exit 0
  204. }
  205. Assert-Command $SshExe
  206. Assert-Command $ScpExe
  207. Write-Step 'Creating remote staging directory over Tailscale/OpenSSH'
  208. $mkdirScript = "New-Item -ItemType Directory -Force -Path $(ConvertTo-SingleQuotedPowerShell $remoteDirectory) | Out-Null"
  209. $mkdirBytes = [Text.Encoding]::Unicode.GetBytes($mkdirScript)
  210. $mkdirEncodedCommand = [Convert]::ToBase64String($mkdirBytes)
  211. & $SshExe -p $RemotePort $RemoteTarget ('powershell.exe -NoProfile -NonInteractive -EncodedCommand ' + $mkdirEncodedCommand)
  212. if ($LASTEXITCODE -ne 0) { throw 'Remote directory creation failed.' }
  213. Write-Step 'Copying the host installer'
  214. & $ScpExe -P $RemotePort $installerPath ($RemoteTarget + ':' + $remoteDirectory.Replace('\', '/') + '/install-iis-release.ps1')
  215. if ($LASTEXITCODE -ne 0) { throw 'Installer copy failed.' }
  216. if (-not $Rollback -and -not $RemotePreflightOnly) {
  217. Write-Step 'Copying the release package'
  218. & $ScpExe -P $RemotePort $packagePath ($RemoteTarget + ':' + $remoteDirectory.Replace('\', '/') + '/' + $ReleaseId + '.zip')
  219. if ($LASTEXITCODE -ne 0) { throw 'Package copy failed.' }
  220. }
  221. Write-Step 'Invoking the host-side installer'
  222. & $SshExe -p $RemotePort $RemoteTarget $remoteCommand
  223. if ($LASTEXITCODE -ne 0) { throw "Remote installer failed with exit code $LASTEXITCODE." }
  224. Write-Step 'Remote operation completed successfully'
  225. } finally {
  226. if ($KeepPackage -and (Test-Path -LiteralPath $packagePath)) {
  227. $keptPath = Join-Path (Get-Location) ([System.IO.Path]::GetFileName($packagePath))
  228. Copy-Item -LiteralPath $packagePath -Destination $keptPath -Force
  229. Write-Host "Package retained at $keptPath"
  230. }
  231. if (Test-Path -LiteralPath $workRoot) {
  232. Remove-Item -LiteralPath $workRoot -Recurse -Force
  233. }
  234. }

Powered by TurnKey Linux.