Files
harats-booking/deploy/deploy.ps1
T
2026-06-08 14:36:51 +07:00

262 lines
9.0 KiB
PowerShell

<#
.SYNOPSIS
Harat's Booking Deploy — builds and sends release to production server.
.DESCRIPTION
1. Bumps version.json with current build timestamp
2. Builds client (unless -SkipBuild)
3. Creates a tar.gz archive (no node_modules, no .env)
4. Uploads to server via SCP
5. Runs deploy-remote.sh on the server via SSH
.PARAMETER Server
Production server IP/hostname.
.PARAMETER User
SSH username on the server.
.PARAMETER Port
SSH port (default 2182).
.PARAMETER SkipBuild
Skip client build (use existing dist/).
.EXAMPLE
.\deploy\deploy.ps1 # full deploy
.\deploy\deploy.ps1 -SkipBuild # skip client build
#>
param(
[string]$Server = "178.169.87.152",
[string]$User = "eugenius",
[int]$Port = 2182,
[switch]$SkipBuild
)
$ErrorActionPreference = "Stop"
$ProjectRoot = Split-Path -Parent $PSScriptRoot # harats/
$DeployDir = "/apps/harats"
# -- Error reporting -------------------------------------------------------
function Write-DeployError {
param(
[string]$Step,
[string]$Message,
[string]$Detail = "",
[string]$Hint = ""
)
Write-Host ""
Write-Host " +============================================+" -ForegroundColor Red
Write-Host " | DEPLOY FAILED |" -ForegroundColor Red
Write-Host " +============================================+" -ForegroundColor Red
Write-Host ""
Write-Host " Step: " -NoNewline -ForegroundColor DarkGray
Write-Host "$Step" -ForegroundColor White
Write-Host " Error: " -NoNewline -ForegroundColor DarkGray
Write-Host "$Message" -ForegroundColor Yellow
if ($Detail) {
Write-Host " Detail: " -NoNewline -ForegroundColor DarkGray
Write-Host "$Detail" -ForegroundColor Gray
}
if ($Hint) {
Write-Host ""
Write-Host " Hint: " -NoNewline -ForegroundColor DarkGray
Write-Host "$Hint" -ForegroundColor Cyan
}
Write-Host ""
}
# -- Main deploy pipeline --------------------------------------------------
$currentStep = "Init"
try {
# -- 1. Read version --
$currentStep = "[1/5] Version read"
$versionFile = Join-Path $ProjectRoot "version.json"
if (-not (Test-Path $versionFile)) {
throw "version.json not found at: $versionFile"
}
$versionData = Get-Content $versionFile -Raw | ConvertFrom-Json
$version = $versionData.version
if (-not $version) { throw "version.json is missing the 'version' field" }
$build = Get-Date -Format "yyyyMMdd-HHmm"
$tag = "${version}+${build}"
Write-Host ""
Write-Host "=======================================" -ForegroundColor Cyan
Write-Host " Harat's Deploy - v${tag}" -ForegroundColor Cyan
Write-Host " Target: ${User}@${Server}:${Port}" -ForegroundColor Cyan
Write-Host "=======================================" -ForegroundColor Cyan
Write-Host ""
# -- 2. Update version.json --
$currentStep = "[1/5] Version update"
$versionData.build = $build
$versionData.buildDate = (Get-Date -Format "o")
$jsonOut = $versionData | ConvertTo-Json -Depth 3
[System.IO.File]::WriteAllText($versionFile, $jsonOut, [System.Text.UTF8Encoding]::new($false))
Write-Host "[1/5] Version: $tag" -ForegroundColor Green
# -- 3. Build client --
$currentStep = "[2/5] Client build"
if (-not $SkipBuild) {
Write-Host "[2/5] Building client..." -ForegroundColor Yellow
# Build shared first
$sharedDir = Join-Path $ProjectRoot "packages\shared"
if (Test-Path (Join-Path $sharedDir "package.json")) {
Push-Location $sharedDir
try {
npm run build 2>$null
} finally {
Pop-Location
}
}
$clientDir = Join-Path $ProjectRoot "client"
if (-not (Test-Path (Join-Path $clientDir "package.json"))) {
throw "client/package.json not found -- is the workspace intact?"
}
Push-Location $clientDir
try {
npm run build
if ($LASTEXITCODE -ne 0) {
throw "Vite build exited with code $LASTEXITCODE -- check the compiler output above"
}
} finally {
Pop-Location
}
# Build server
Write-Host " Building server..." -ForegroundColor Yellow
$serverDir = Join-Path $ProjectRoot "server"
Push-Location $serverDir
try {
npm run build
if ($LASTEXITCODE -ne 0) {
throw "Server tsc build exited with code $LASTEXITCODE"
}
} finally {
Pop-Location
}
Write-Host " Build OK" -ForegroundColor Green
} else {
Write-Host "[2/5] Skipping build" -ForegroundColor DarkGray
}
# -- 4. Create archive --
$currentStep = "[3/5] Archive creation"
Write-Host "[3/5] Creating archive..." -ForegroundColor Yellow
$archiveName = "harats-${tag}.tar.gz"
$archivePath = Join-Path $ProjectRoot $archiveName
# Verify git is available (needed for GNU tar)
$gitCmd = Get-Command git -ErrorAction SilentlyContinue
if (-not $gitCmd) {
throw "git not found in PATH -- GNU tar from Git for Windows is required to create archives"
}
$gitRoot = $gitCmd.Source | Split-Path | Split-Path
$gnuTar = Join-Path $gitRoot "usr\bin\tar.exe"
if (-not (Test-Path $gnuTar)) {
throw "GNU tar not found at: $gnuTar -- expected Git for Windows installation"
}
Push-Location $ProjectRoot
try {
$oldPath = $env:PATH
$env:PATH = "$gitRoot\usr\bin;$env:PATH"
& $gnuTar -czf $archiveName `
--exclude="node_modules" `
--exclude="*.tar.gz" `
--exclude=".git" `
--exclude="envs/prod/.env" `
server/dist/ `
server/prisma/ `
server/package.json `
server/package-lock.json `
client/dist/ `
packages/shared/ `
Dockerfile `
.dockerignore `
docker-compose.prod.yml `
ecosystem.config.cjs `
version.json `
envs/ `
deploy/
if ($LASTEXITCODE -ne 0) {
throw "tar exited with code $LASTEXITCODE -- check file list for missing paths"
}
$env:PATH = $oldPath
} finally {
Pop-Location
}
if (-not (Test-Path $archivePath)) {
throw "Archive was not created at: $archivePath"
}
$sizeMB = [math]::Round((Get-Item $archivePath).Length / 1MB, 1)
if ($sizeMB -lt 0.1) {
Write-Host " Warning: archive is suspiciously small (${sizeMB} MB)" -ForegroundColor DarkYellow
}
Write-Host " Archive: $archiveName - ${sizeMB} MB" -ForegroundColor Green
# -- 5. Upload --
$currentStep = "[4/5] SCP upload"
Write-Host "[4/5] Uploading to ${Server}..." -ForegroundColor Yellow
scp -P $Port $archivePath "${User}@${Server}:${DeployDir}/releases/"
if ($LASTEXITCODE -ne 0) {
$hint = switch ($LASTEXITCODE) {
255 { "Connection refused or auth failed. Check SSH key, port ($Port), and server availability." }
1 { "SCP error -- target directory ${DeployDir}/releases/ may not exist on the server." }
default { "SCP exited with code $LASTEXITCODE." }
}
throw $hint
}
Write-Host " Upload OK" -ForegroundColor Green
# -- 6. Remote deploy --
$currentStep = "[5/5] Remote deploy (SSH)"
Write-Host "[5/5] Deploying on server..." -ForegroundColor Yellow
ssh -p $Port "${User}@${Server}" "cd ${DeployDir}; cp deploy/deploy-remote.sh /tmp/_harats-deploy.sh; bash /tmp/_harats-deploy.sh '${archiveName}' '${tag}'"
if ($LASTEXITCODE -ne 0) {
$hint = switch ($LASTEXITCODE) {
255 { "SSH connection failed. Is the server reachable at ${Server}:${Port}?" }
126 { "deploy-remote.sh exists but is not executable." }
127 { "deploy-remote.sh not found on server at ${DeployDir}/deploy/deploy-remote.sh" }
default { "Remote script exited with code $LASTEXITCODE -- check server logs" }
}
throw $hint
}
# -- Cleanup local archive --
$currentStep = "Cleanup"
Remove-Item $archivePath -Force -ErrorAction SilentlyContinue
Write-Host ""
Write-Host "=======================================" -ForegroundColor Green
Write-Host " Deployed Harat's v${tag}" -ForegroundColor Green
Write-Host "=======================================" -ForegroundColor Green
Write-Host ""
} catch {
Write-DeployError `
-Step $currentStep `
-Message $_.Exception.Message `
-Detail ($_.ScriptStackTrace -split "`n" | Select-Object -First 3 | Out-String).Trim() `
-Hint "Fix the issue above and re-run: .\deploy\deploy.ps1$(if ($SkipBuild) { ' -SkipBuild' })"
# Cleanup partial archive if it exists
if ($archivePath -and (Test-Path $archivePath -ErrorAction SilentlyContinue)) {
Remove-Item $archivePath -Force -ErrorAction SilentlyContinue
}
exit 1
}