Jenkinsfile.prod 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. // =====================================================================
  2. // ANTOOZA Frontend Jenkinsfile (PROD)
  3. // - Branches: main only (dev 는 Frontend/Jenkinsfile 사용)
  4. // - Agent: PROD-Window-Server (Node v24)
  5. // - Deploy path: C:\workspace\IIS\Front
  6. // - Process: WinSW service 'antooza-frontend' (node server.js on :3000)
  7. // - IIS: ANTOOZA-Front site reverse-proxies HTTPS:443 -> 127.0.0.1:3000
  8. // =====================================================================
  9. pipeline {
  10. agent { label 'PROD-Window-Server' }
  11. options {
  12. timestamps()
  13. disableConcurrentBuilds()
  14. buildDiscarder(logRotator(numToKeepStr: '20'))
  15. }
  16. environment {
  17. DEPLOY_ROOT = 'C:\\workspace\\IIS\\Front'
  18. RELEASES = 'C:\\workspace\\IIS\\_releases'
  19. SVC_NAME = 'antooza-frontend'
  20. }
  21. stages {
  22. stage('Install') {
  23. steps {
  24. bat 'chcp 65001 >NUL && node -v && npm -v'
  25. script {
  26. def lockChanged = bat(returnStatus: true,
  27. script: '@git diff --quiet HEAD~1 HEAD -- package-lock.json 2>nul') != 0
  28. def hasModules = fileExists('node_modules/.package-lock.json')
  29. if (!hasModules || lockChanged) {
  30. echo "Installing (lockChanged=${lockChanged}, hasModules=${hasModules})"
  31. bat 'chcp 65001 >NUL && npm ci --no-audit --no-fund --prefer-offline'
  32. } else {
  33. echo 'package-lock.json unchanged + node_modules present — skip npm ci'
  34. }
  35. }
  36. }
  37. }
  38. stage('Inject Env (Secret File)') {
  39. steps {
  40. script {
  41. // PROD 전용 — 항상 PROD-ANTOOZA-FRONTEND-ENV 사용
  42. def credId = 'PROD-ANTOOZA-FRONTEND-ENV'
  43. echo "[env] Using Jenkins Secret File: ${credId}"
  44. withCredentials([file(credentialsId: credId, variable: 'CFG')]) {
  45. bat 'chcp 65001 >NUL && copy /Y "%CFG%" .env.production >NUL && for %%I in (.env.production) do @echo [env] .env.production injected (%%~zI bytes)'
  46. }
  47. }
  48. }
  49. }
  50. stage('Build') {
  51. steps {
  52. bat 'chcp 65001 >NUL && npm run build'
  53. }
  54. }
  55. stage('Deploy') {
  56. steps {
  57. script { deployFrontend() }
  58. }
  59. }
  60. }
  61. post {
  62. success { echo "PROD Frontend build & deploy completed" }
  63. failure { echo 'PROD Frontend build/deploy failed - check stage logs' }
  64. }
  65. }
  66. def deployFrontend() {
  67. powershell """
  68. \$ErrorActionPreference = 'Stop'
  69. \$ts = Get-Date -Format 'yyyyMMdd-HHmmss'
  70. \$dest = '${env.DEPLOY_ROOT}'
  71. \$backup = Join-Path '${env.RELEASES}' ('${env.SVC_NAME}-' + \$ts)
  72. if (-not (Test-Path '${env.RELEASES}')) { New-Item -ItemType Directory -Path '${env.RELEASES}' -Force | Out-Null }
  73. Write-Host '[${env.SVC_NAME}] Stopping service...'
  74. Stop-Service ${env.SVC_NAME} -Force -ErrorAction SilentlyContinue
  75. Start-Sleep -Seconds 2
  76. # Preserve infrastructure files (WinSW + IIS web.config + logs)
  77. \$keep = @('${env.SVC_NAME}.exe', '${env.SVC_NAME}.xml', 'web.config',
  78. '${env.SVC_NAME}.err.log', '${env.SVC_NAME}.out.log', '${env.SVC_NAME}.wrapper.log')
  79. if ((Test-Path \$dest) -and ((Get-ChildItem \$dest -Force | Measure-Object).Count -gt 0)) {
  80. Write-Host ('[${env.SVC_NAME}] Backing up current to ' + \$backup)
  81. New-Item -ItemType Directory -Path \$backup -Force | Out-Null
  82. Get-ChildItem \$dest -Force | Where-Object { \$_.Name -notin \$keep } |
  83. Move-Item -Destination \$backup -Force
  84. } elseif (-not (Test-Path \$dest)) {
  85. New-Item -ItemType Directory -Path \$dest -Force | Out-Null
  86. }
  87. # Next.js standalone layout
  88. Write-Host '[${env.SVC_NAME}] Copying standalone server...'
  89. Copy-Item '.next\\standalone\\*' \$dest -Recurse -Force
  90. Write-Host '[${env.SVC_NAME}] Copying static assets...'
  91. New-Item -ItemType Directory -Path (Join-Path \$dest '.next\\static') -Force | Out-Null
  92. Copy-Item '.next\\static\\*' (Join-Path \$dest '.next\\static') -Recurse -Force
  93. Write-Host '[${env.SVC_NAME}] Copying public assets...'
  94. New-Item -ItemType Directory -Path (Join-Path \$dest 'public') -Force | Out-Null
  95. if (Test-Path 'public') {
  96. Copy-Item 'public\\*' (Join-Path \$dest 'public') -Recurse -Force
  97. }
  98. if (Test-Path '.env.production') {
  99. Write-Host '[${env.SVC_NAME}] Copying .env.production...'
  100. Copy-Item '.env.production' (Join-Path \$dest '.env.production') -Force
  101. } else {
  102. Write-Warning '[${env.SVC_NAME}] .env.production missing in workspace'
  103. }
  104. # First-deploy auto-install WinSW
  105. if (-not (Get-Service ${env.SVC_NAME} -ErrorAction SilentlyContinue)) {
  106. Write-Host '[${env.SVC_NAME}] Service not registered - installing'
  107. \$installer = Join-Path \$dest '${env.SVC_NAME}.exe'
  108. if (-not (Test-Path \$installer)) {
  109. throw ('[${env.SVC_NAME}] WinSW binary missing at ' + \$installer)
  110. }
  111. & \$installer install
  112. if (\$LASTEXITCODE -ne 0) { throw ('[${env.SVC_NAME}] install failed (exit ' + \$LASTEXITCODE + ')') }
  113. Start-Sleep -Seconds 2
  114. }
  115. Write-Host '[${env.SVC_NAME}] Starting service...'
  116. Start-Service ${env.SVC_NAME}
  117. # Health check
  118. \$ok = \$false
  119. 1..15 | ForEach-Object {
  120. Start-Sleep -Seconds 3
  121. try {
  122. \$r = Invoke-WebRequest -Uri 'http://127.0.0.1:3000/' -UseBasicParsing -TimeoutSec 5 -MaximumRedirection 0
  123. if (\$r.StatusCode -ge 200 -and \$r.StatusCode -lt 400) {
  124. Write-Host ('[${env.SVC_NAME}] Healthy (HTTP ' + \$r.StatusCode + ') on attempt ' + \$_)
  125. \$ok = \$true; return
  126. }
  127. } catch {
  128. \$resp = \$_.Exception.Response
  129. if (\$resp) {
  130. \$code = [int]\$resp.StatusCode
  131. if (\$code -ge 200 -and \$code -lt 400) {
  132. Write-Host ('[${env.SVC_NAME}] Healthy (HTTP ' + \$code + ') on attempt ' + \$_)
  133. \$ok = \$true; return
  134. }
  135. }
  136. Write-Host ('[${env.SVC_NAME}] attempt ' + \$_ + ' failed: ' + \$_.Exception.Message)
  137. }
  138. }
  139. if (-not \$ok) {
  140. Write-Warning '[${env.SVC_NAME}] Verification failed - rolling back'
  141. Stop-Service ${env.SVC_NAME} -Force -ErrorAction SilentlyContinue
  142. if (Test-Path \$backup) {
  143. Get-ChildItem \$dest -Force | Where-Object { \$_.Name -notin \$keep } |
  144. Remove-Item -Recurse -Force -ErrorAction SilentlyContinue
  145. Copy-Item (Join-Path \$backup '*') \$dest -Recurse -Force
  146. Start-Service ${env.SVC_NAME}
  147. }
  148. throw '${env.SVC_NAME} deployment failed; rolled back.'
  149. }
  150. Get-ChildItem '${env.RELEASES}' -Directory -ErrorAction SilentlyContinue |
  151. Where-Object { \$_.Name -like '${env.SVC_NAME}-*' } |
  152. Sort-Object LastWriteTime -Descending |
  153. Select-Object -Skip 5 |
  154. Remove-Item -Recurse -Force
  155. """
  156. }