# Build and start the MyOffice demo stack (SPA + API + Postgres + pgAdmin). # Usage: # .\build.ps1 # front 32080, back 32081, pgAdmin 32082 # .\build.ps1 32080 32081 32082 # override ports # .\build.ps1 build | up | down | logs # .\build.ps1 rebuild # --no-cache images + recreate containers # .\build.ps1 reset # wipe ./data/demo/*, rebuild, start (new demo passwords) $ErrorActionPreference = 'Stop' $here = Split-Path -Parent $MyInvocation.MyCommand.Path Set-Location $here $composeFile = 'docker-compose.demo.yml' $knownActions = @('build', 'up', 'down', 'logs', 'all', 'rebuild', 'reset') function Test-Port([string]$value) { return $value -match '^\d{1,5}$' -and [int]$value -ge 1 -and [int]$value -le 65535 } function Format-Origin([string]$port) { if ($port -eq '80') { return 'http://localhost' } return "http://localhost:$port" } function Show-UpInfo { param([string]$FrontOrigin, [string]$ApiPublicUrl, [string]$PgAdminUrl) Write-Host '' Write-Host 'Demo is up:' Write-Host " Frontend $FrontOrigin" Write-Host " Backend $ApiPublicUrl" Write-Host " pgAdmin $PgAdminUrl (admin@example.com / admin; DB auto-connected)" Write-Host ' Postgres internal host=postgres user/pass/db=myoffice' Write-Host '' Write-Host 'Demo logins: user_UAH@user_UAH.userUAH / user_UAH (also USD, EUR)' } function Invoke-DockerCompose { param([Parameter(ValueFromRemainingArguments = $true)][string[]]$ComposeArgs) # Docker writes progress to stderr; with ErrorAction Stop that aborts the script. $prev = $ErrorActionPreference $ErrorActionPreference = 'Continue' try { & docker compose -f $composeFile @ComposeArgs if ($LASTEXITCODE -ne 0) { throw "docker compose failed with exit code $LASTEXITCODE" } } finally { $ErrorActionPreference = $prev } } # Parse: [action] [frontPort] [backPort] [pgAdminPort] # Numeric args arrive as Int32 — always normalize to string first. $action = 'all' $frontPort = '32080' $backPort = '32081' $pgAdminPort = '32082' $rest = @($args | ForEach-Object { "$_" }) if ($rest.Count -gt 0 -and $knownActions -contains $rest[0].ToLowerInvariant()) { $action = $rest[0].ToLowerInvariant() $rest = @($rest | Select-Object -Skip 1) } elseif ($rest.Count -gt 0 -and (Test-Port $rest[0])) { $action = 'all' } if ($rest.Count -ge 1) { if (-not (Test-Port $rest[0])) { Write-Error "Invalid front port '$($rest[0])'. Example: .\build.ps1 32080 32081 32082" } $frontPort = [string]$rest[0] } if ($rest.Count -ge 2) { if (-not (Test-Port $rest[1])) { Write-Error "Invalid back port '$($rest[1])'. Example: .\build.ps1 32080 32081 32082" } $backPort = [string]$rest[1] } if ($rest.Count -ge 3) { if (-not (Test-Port $rest[2])) { Write-Error "Invalid pgAdmin port '$($rest[2])'. Example: .\build.ps1 32080 32081 32082" } $pgAdminPort = [string]$rest[2] } if ($rest.Count -gt 3 -and $action -ne 'down' -and $action -ne 'logs') { Write-Error 'Too many arguments. Usage: .\build.ps1 [build|up|down|logs|rebuild|reset] [frontPort] [backPort] [pgAdminPort]' } $frontOrigin = Format-Origin $frontPort $apiPublicUrl = "http://localhost:$backPort" $pgAdminUrl = Format-Origin $pgAdminPort $env:MYOFFICE_WEB_PORT = $frontPort $env:MYOFFICE_API_PORT = $backPort $env:MYOFFICE_PGADMIN_PORT = $pgAdminPort $env:MYOFFICE_FRONTEND_HOST = $frontOrigin $env:MYOFFICE_API_PUBLIC_URL = $apiPublicUrl $env:MYOFFICE_CORS_0 = $frontOrigin $env:MYOFFICE_CORS_1 = if ($frontPort -eq '80') { 'http://127.0.0.1' } else { "http://127.0.0.1:$frontPort" } # Avoid stale compose env / cached layers leaving old API code running. $env:COMPOSE_DOCKER_CLI_BUILD = '1' $env:DOCKER_BUILDKIT = '1' switch ($action) { 'build' { Invoke-DockerCompose build } 'up' { New-Item -ItemType Directory -Path (Join-Path $here 'data\demo\postgres') -Force | Out-Null New-Item -ItemType Directory -Path (Join-Path $here 'data\demo\pgadmin') -Force | Out-Null Invoke-DockerCompose up -d --build --force-recreate --remove-orphans Show-UpInfo $frontOrigin $apiPublicUrl $pgAdminUrl } 'down' { Invoke-DockerCompose down } 'logs' { Invoke-DockerCompose logs -f } 'rebuild' { Write-Host 'Rebuilding images with --no-cache (api + web)...' Invoke-DockerCompose build --no-cache --pull Invoke-DockerCompose up -d --force-recreate --remove-orphans Show-UpInfo $frontOrigin $apiPublicUrl $pgAdminUrl } 'reset' { Write-Host 'Stopping stack and wiping ./data/demo/postgres and ./data/demo/pgadmin...' Invoke-DockerCompose down foreach ($dir in @('data\demo\postgres', 'data\demo\pgadmin')) { $path = Join-Path $here $dir if (Test-Path $path) { Remove-Item -LiteralPath $path -Recurse -Force } New-Item -ItemType Directory -Path $path -Force | Out-Null } # Drop old named volumes if they still exist from earlier setups. docker volume rm myoffice-demo_myoffice_pgdata myoffice-demo_myoffice_pgadmin 2>$null | Out-Null Write-Host 'Rebuilding images with --no-cache...' Invoke-DockerCompose build --no-cache --pull Invoke-DockerCompose up -d --force-recreate --remove-orphans Show-UpInfo $frontOrigin $apiPublicUrl $pgAdminUrl Write-Host '' Write-Host 'DB was reset. Login with password user_UAH (not user1_UAH).' Write-Host 'Data folders: Docker\data\demo\postgres , Docker\data\demo\pgadmin' } 'all' { New-Item -ItemType Directory -Path (Join-Path $here 'data\demo\postgres') -Force | Out-Null New-Item -ItemType Directory -Path (Join-Path $here 'data\demo\pgadmin') -Force | Out-Null Invoke-DockerCompose up -d --build --force-recreate --remove-orphans Show-UpInfo $frontOrigin $apiPublicUrl $pgAdminUrl } default { Write-Error "Unknown action '$action'. Use: build, up, down, logs, rebuild, reset, or no args." } }