Files
nova-deploy-automatizer/deploy.ps1
T
Josias Castro 8480111728 Initial commit
2026-06-18 17:20:35 -03:00

347 lines
11 KiB
PowerShell

param(
[Parameter(Mandatory = $true)]
[string]$Version,
[Parameter(Mandatory = $true)]
[string]$ZipPath,
[string]$ReportsZipPath = "",
[Parameter(Mandatory = $true)]
[string]$TargetId,
[string]$TargetsPath = ""
)
$ErrorActionPreference = "Stop"
function Write-Step {
param([string]$Message)
Write-Host "[INFO] $Message"
}
function Write-Success {
param([string]$Message)
Write-Host "[SUCCESS] $Message"
}
function Get-ToolPath {
param(
[Parameter(Mandatory = $true)]
[string]$FileName,
[string[]]$FallbackPaths = @()
)
$localPath = Join-Path $PSScriptRoot $FileName
if (Test-Path $localPath) { return $localPath }
foreach ($path in $FallbackPaths) {
if (Test-Path $path) { return $path }
}
$command = Get-Command $FileName -ErrorAction SilentlyContinue
if ($command) { return $command.Source }
throw "No se encontro $FileName. Copialo junto a la app o instalalo con PuTTY."
}
function Get-ZipEntryNames {
param(
[Parameter(Mandatory = $true)]
[string]$Path
)
Add-Type -AssemblyName System.IO.Compression.FileSystem
$zip = [System.IO.Compression.ZipFile]::OpenRead($Path)
try {
return @(
$zip.Entries |
Where-Object { -not [string]::IsNullOrWhiteSpace($_.FullName) } |
ForEach-Object { $_.FullName.Replace("\", "/") } |
Where-Object {
$_ -and
$_ -notmatch "^__MACOSX/" -and
$_ -notmatch "(^|/)\.DS_Store$"
}
)
}
finally {
$zip.Dispose()
}
}
function ConvertTo-ShellArg {
param(
[Parameter(Mandatory = $true)]
[string]$Value
)
$singleQuote = [string][char]39
return $singleQuote + $Value.Replace($singleQuote, "$singleQuote\$singleQuote$singleQuote") + $singleQuote
}
function Invoke-Plink {
param(
[Parameter(Mandatory = $true)]
[string]$Command
)
& $script:Plink -ssh "$($script:Target.user)@$($script:Target.host)" -P $script:Target.port -pw $script:Target.password -batch $Command 2>&1
}
if ([string]::IsNullOrWhiteSpace($TargetsPath)) {
$TargetsPath = Join-Path $PSScriptRoot "targets.json"
}
if (-not (Test-Path $TargetsPath)) {
throw "No se encontro el archivo de destinos: $TargetsPath"
}
if (-not (Test-Path $ZipPath)) {
throw "No se encontro el ZIP principal: $ZipPath"
}
$targetsConfig = Get-Content -Raw -Path $TargetsPath | ConvertFrom-Json
$targetMatches = @($targetsConfig.targets | Where-Object { $_.id -eq $TargetId })
if ($targetMatches.Count -eq 0) {
throw "No existe el destino seleccionado: $TargetId"
}
$script:Target = $targetMatches[0]
$script:Plink = Get-ToolPath -FileName "plink.exe" -FallbackPaths @(
"C:\Program Files\PuTTY\plink.exe",
"C:\Program Files (x86)\PuTTY\plink.exe"
)
$pscp = Get-ToolPath -FileName "pscp.exe" -FallbackPaths @(
"C:\Program Files\PuTTY\pscp.exe",
"C:\Program Files (x86)\PuTTY\pscp.exe"
)
$zipFileName = Split-Path -Leaf $ZipPath
$reportesFolderName = "reportes_subidos_jasper"
$hasReportsZipPath = -not [string]::IsNullOrWhiteSpace($ReportsZipPath)
if ($hasReportsZipPath -and -not (Test-Path $ReportsZipPath)) {
throw "No se encontro el ZIP de reportes: $ReportsZipPath"
}
Write-Step "Version indicada: $Version"
Write-Step "Destino: $($Target.name) ($($Target.host):$($Target.port))"
Write-Step "Validando estructura del ZIP principal..."
$zipEntryNames = Get-ZipEntryNames -Path $ZipPath
if ($zipEntryNames.Count -eq 0) {
throw "El ZIP principal esta vacio: $ZipPath"
}
$topLevelNames = @(
$zipEntryNames |
ForEach-Object { ($_ -split "/", 2)[0] } |
Where-Object { $_ } |
Sort-Object -Unique
)
$topLevelFiles = @(
$zipEntryNames |
Where-Object { ($_ -notmatch "/") -and (-not $_.EndsWith("/")) }
)
$zipRootPrefix = $null
if (($topLevelNames.Count -eq 1) -and ($topLevelFiles.Count -eq 0)) {
$zipRootPrefix = $topLevelNames[0]
Write-Step "El ZIP contiene una carpeta raiz ($zipRootPrefix). Se desplegara su contenido."
}
$deployEntryNames = $zipEntryNames
if ($zipRootPrefix) {
$rootPrefixPattern = "^" + [regex]::Escape($zipRootPrefix.TrimEnd("/")) + "/"
$deployEntryNames = @(
$zipEntryNames |
Where-Object { $_ -match $rootPrefixPattern } |
ForEach-Object { $_ -replace $rootPrefixPattern, "" } |
Where-Object { $_ }
)
}
$deployTopLevelFolders = @(
$deployEntryNames |
Where-Object { $_ -match "/" } |
ForEach-Object { ($_ -split "/", 2)[0] } |
Where-Object { $_ } |
Sort-Object -Unique
)
if ($deployTopLevelFolders.Count -le 1) {
throw "El contenido a desplegar debe tener mas de una carpeta en el nivel padre."
}
$hasReportesFolder = @(
$deployEntryNames |
Where-Object {
$_ -eq "$reportesFolderName/" -or
$_ -like "$reportesFolderName/*"
}
).Count -gt 0
$needsReportesZip = -not $hasReportesFolder
$reportesZipExtractMode = "direct"
$reportesZipName = ""
if ($needsReportesZip) {
if (-not $hasReportsZipPath) {
Write-Step "El ZIP no incluye $reportesFolderName y no se selecciono ZIP auxiliar. Se continua sin reportes."
}
else {
$reportesZipName = Split-Path -Leaf $ReportsZipPath
$reportesEntryNames = Get-ZipEntryNames -Path $ReportsZipPath
if ($reportesEntryNames.Count -eq 0) {
throw "El ZIP de reportes esta vacio: $ReportsZipPath"
}
$reportesZipHasFolder = @(
$reportesEntryNames |
Where-Object {
$_ -eq "$reportesFolderName/" -or
$_ -like "$reportesFolderName/*"
}
).Count -gt 0
if (-not $reportesZipHasFolder) {
$reportesZipExtractMode = "into-folder"
Write-Step "El ZIP de reportes no contiene carpeta raiz. Se extraera dentro de $reportesFolderName."
}
}
}
$remoteUploadPath = $Target.remoteUploadPath
$remoteAppPath = $Target.remoteAppPath
$backupDir = $Target.backupDir
$jasperstarterPath = "$remoteAppPath/_lib/libraries/grp/composer/vendor/geekcom/phpjasper/bin/jasperstarter/bin/jasperstarter"
$remoteTempPath = "$remoteUploadPath/gt_nova_deploy_tmp_$Version"
$remoteZipPath = "$remoteUploadPath/$zipFileName"
$remoteReportesZipPath = if ($reportesZipName) { "$remoteUploadPath/$reportesZipName" } else { "" }
$remoteExtractSource = if ($zipRootPrefix) { "$remoteTempPath/$zipRootPrefix" } else { $remoteTempPath }
$remoteAppArg = ConvertTo-ShellArg $remoteAppPath
$remoteTempArg = ConvertTo-ShellArg $remoteTempPath
$remoteZipArg = ConvertTo-ShellArg $remoteZipPath
$remoteExtractSourceArg = ConvertTo-ShellArg $remoteExtractSource
$remoteReportesFolderArg = ConvertTo-ShellArg "$remoteAppPath/$reportesFolderName"
$remoteReportesZipArg = if ($remoteReportesZipPath) { ConvertTo-ShellArg $remoteReportesZipPath } else { "" }
$jasperstarterArg = ConvertTo-ShellArg $jasperstarterPath
Write-Step "Probando conexion SSH..."
$connectionCheck = Invoke-Plink -Command "echo SSH_OK"
if ($connectionCheck -notmatch "SSH_OK") {
throw "No se pudo conectar por SSH. Salida: $connectionCheck"
}
Write-Success "Conexion SSH OK."
$timestamp = Get-Date -Format "yyyyMMdd_HHmmss"
$backupFileName = "backup_gt_nova_${Version}_$timestamp.tar.gz"
$backupCommand = "mkdir -p $(ConvertTo-ShellArg $backupDir) && tar -czf $(ConvertTo-ShellArg "$backupDir/$backupFileName") -C /var/www/html $(Split-Path -Leaf $remoteAppPath) && echo BACKUP_OK"
Write-Step "Creando backup remoto..."
$backupOutput = Invoke-Plink -Command $backupCommand
if ($backupOutput -notmatch "BACKUP_OK") {
throw "Error al crear backup remoto. Salida: $backupOutput"
}
Write-Success "Backup creado: $backupDir/$backupFileName"
Write-Step "Subiendo ZIP principal..."
& $pscp -P $Target.port -pw $Target.password $ZipPath "$($Target.user)@$($Target.host):$remoteUploadPath/" 2>&1 | ForEach-Object { Write-Host $_ }
if ($LASTEXITCODE -ne 0) {
throw "Error al subir el ZIP principal."
}
Write-Success "ZIP principal subido."
$shouldUploadReportsZip = $needsReportesZip -and $hasReportsZipPath
if ($shouldUploadReportsZip) {
Write-Step "Subiendo ZIP auxiliar de reportes..."
& $pscp -P $Target.port -pw $Target.password $ReportsZipPath "$($Target.user)@$($Target.host):$remoteUploadPath/" 2>&1 | ForEach-Object { Write-Host $_ }
if ($LASTEXITCODE -ne 0) {
throw "Error al subir el ZIP de reportes."
}
Write-Success "ZIP de reportes subido."
}
$cmdReportes = ":"
if ($shouldUploadReportsZip -and $reportesZipExtractMode -eq "direct") {
$cmdReportes = "sudo unzip -oq $remoteReportesZipArg -d $remoteAppArg"
}
elseif ($shouldUploadReportsZip -and $reportesZipExtractMode -eq "into-folder") {
$cmdReportes = @"
sudo mkdir -p $remoteReportesFolderArg
sudo unzip -oq $remoteReportesZipArg -d $remoteReportesFolderArg
"@
}
Write-Step "Descomprimiendo y reemplazando aplicacion..."
$deployCommand = @"
set -e
sudo rm -rf $remoteTempArg
sudo mkdir -p $remoteTempArg $remoteAppArg
sudo unzip -q $remoteZipArg -d $remoteTempArg
sudo find $remoteAppArg -mindepth 1 -maxdepth 1 -exec rm -rf {} +
sudo cp -a $remoteExtractSourceArg/. $remoteAppArg/
$cmdReportes
sudo rm -rf $remoteTempArg
echo UNZIP_OK
"@
$deployOutput = Invoke-Plink -Command $deployCommand
if ($deployOutput -notmatch "UNZIP_OK") {
throw "Error al descomprimir o reemplazar aplicacion. Salida: $deployOutput"
}
Write-Success "Aplicacion reemplazada."
$serviceRestartLines = @()
foreach ($service in @($Target.services)) {
if (-not [string]::IsNullOrWhiteSpace($service)) {
$serviceRestartLines += "sudo systemctl restart $(ConvertTo-ShellArg $service)"
}
}
$cleanupReportes = if ($shouldUploadReportsZip) { "sudo rm -f $remoteReportesZipArg" } else { ":" }
$fixJasperstarterPermissions = ":"
if ($Target.host -eq "4.246.183.98") {
$fixJasperstarterPermissions = @"
if [ -f $jasperstarterArg ]; then
sudo chmod 755 $jasperstarterArg
ls -l $jasperstarterArg
else
echo JASPERSTARTER_NOT_FOUND
exit 1
fi
"@
}
$postDeployCommand = @"
set -e
sudo chown -R www-data:www-data $remoteAppArg
sudo find $remoteAppArg -type d -exec chmod 755 {} \;
sudo find $remoteAppArg -type f -exec chmod 644 {} \;
$fixJasperstarterPermissions
$($serviceRestartLines -join "`n")
sudo rm -f $remoteZipArg
$cleanupReportes
echo DEPLOY_OK
"@
Write-Step "Aplicando permisos, reiniciando servicios y limpiando temporales..."
$postDeployOutput = Invoke-Plink -Command $postDeployCommand
if ($postDeployOutput -notmatch "DEPLOY_OK") {
throw "El deploy reemplazo archivos, pero fallo el post-deploy. Salida: $postDeployOutput"
}
if ($Target.host -eq "4.246.183.98") {
if ($postDeployOutput -notmatch "jasperstarter") {
throw "El deploy finalizo, pero no se pudo verificar el permiso de jasperstarter. Salida: $postDeployOutput"
}
Write-Success "Permiso 755 aplicado a jasperstarter."
}
Write-Success "Deploy finalizado correctamente para version $Version en $($Target.name)."