param( [string]$HostIp = $env:MOTORSIGNAL_PROD_HOST, [string]$User = $(if ($env:MOTORSIGNAL_PROD_USER) { $env:MOTORSIGNAL_PROD_USER } else { "motorsignal-admin" }), [string]$Key = $env:MOTORSIGNAL_PROD_SSH_KEY, [switch]$BuildRemote ) $ErrorActionPreference = "Stop" if (-not $HostIp) { Write-Error "HostIp parameter or MOTORSIGNAL_PROD_HOST environment variable is required." exit 1 } $SshKeyArgs = @() if ($Key) { if (-not (Test-Path $Key)) { Write-Error "Specified SSH key file not found: $Key" exit 1 } $SshKeyArgs = @("-i", $Key) } $RepositoryRoot = (Resolve-Path "$PSScriptRoot\..").Path $GitSha = (git -C $RepositoryRoot rev-parse HEAD).Trim() if ($GitSha -notmatch '^[0-9a-f]{40}$') { Write-Error "Unable to resolve a valid 40-character Git revision." exit 1 } # Refuse dirty website inputs before assigning the HEAD revision $DirtyWebsite = "$((git -C $RepositoryRoot status --porcelain -- website))".Trim() if ($DirtyWebsite) { Write-Error "Cannot deploy website: working tree contains uncommitted changes under website/:`n$DirtyWebsite" exit 1 } $Timestamp = Get-Date -Format "yyyyMMdd-HHmmss" Write-Host ">>> Deploying website revision $GitSha" -ForegroundColor Cyan if ($BuildRemote) { # Ultra-lean mode: Upload only source files (~100 KB total) $TarName = "website-src-$Timestamp.tar.gz" $TempTar = "$PSScriptRoot\$TarName" Write-Host ">>> 1. Packaging source code (~100 KB)..." -ForegroundColor Cyan tar -czf "$TempTar" -C "$RepositoryRoot\website" app components lib public package.json tsconfig.json next.config.ts Write-Host ">>> 2. Uploading source code to server..." -ForegroundColor Cyan scp @SshKeyArgs "$TempTar" "${User}@${HostIp}:/tmp/$TarName" Remove-Item "$TempTar" -Force Write-Host ">>> 3. Building and restarting on server..." -ForegroundColor Cyan $RemoteCmd = @" bash -c ' set -euo pipefail TAR=/tmp/$TarName TARGET=/opt/motorsignal/current/website echo "Updating source files in `$TARGET..." sudo tar -xzf "`$TAR" -C "`$TARGET" rm -f "`$TAR" cd "`$TARGET" echo "Running Next.js production build on server..." MOTORSIGNAL_GIT_SHA="$GitSha" npm run build echo "Restarting motorsignal-website service..." sudo systemctl restart motorsignal-website echo "Done!" ' "@ ssh -t @SshKeyArgs "${User}@${HostIp}" $RemoteCmd } else { # Fast local build: Strip Next.js cache & dev artifacts (~2 MB total instead of 500 MB) Write-Host ">>> 1. Building Next.js application locally..." -ForegroundColor Cyan Set-Location -Path "$PSScriptRoot\..\website" $PreviousDeploySha = $env:MOTORSIGNAL_GIT_SHA try { $env:MOTORSIGNAL_GIT_SHA = $GitSha npm run build if (Test-Path "$PSScriptRoot\..\website\.next\standalone\server.js") { Copy-Item "$PSScriptRoot\..\website\.next\standalone\server.js" "$PSScriptRoot\..\website\server.js" -Force } } finally { if ($null -eq $PreviousDeploySha) { Remove-Item Env:MOTORSIGNAL_GIT_SHA -ErrorAction SilentlyContinue } else { $env:MOTORSIGNAL_GIT_SHA = $PreviousDeploySha } Set-Location -Path $RepositoryRoot } $TarName = "website-update-$Timestamp.tar.gz" $TempTar = "$PSScriptRoot\$TarName" $TarArgs = @("-czf", "$TempTar", "--exclude=cache", "--exclude=dev", "--exclude=standalone", "-C", "$RepositoryRoot\website", ".next/server", ".next/static", ".next/types", ".next/BUILD_ID", ".next/*.json", "public", "package.json", "app", "components", "lib") if (Test-Path "$RepositoryRoot\website\server.js") { $TarArgs += "server.js" } tar @TarArgs Remove-Item "$RepositoryRoot\website\server.js" -Force -ErrorAction SilentlyContinue $SizeMb = [math]::Round(((Get-Item "$TempTar").Length / 1MB), 2) Write-Host ">>> 3. Uploading $SizeMb MB to server..." -ForegroundColor Cyan scp @SshKeyArgs "$TempTar" "${User}@${HostIp}:/tmp/$TarName" Remove-Item "$TempTar" -Force Write-Host ">>> 4. Activating update on server..." -ForegroundColor Cyan $BashScript = @" set -euo pipefail TAR=/tmp/$TarName TARGET=/opt/motorsignal/current/website echo "Extracting runtime to `$TARGET..." tar -xzf "`$TAR" --no-same-owner --no-same-permissions --no-overwrite-dir -C "`$TARGET" rm -f "`$TAR" echo "Restarting motorsignal-website service..." sudo systemctl restart motorsignal-website echo "Service restarted." "@.Replace("`r", "") ssh @SshKeyArgs "${User}@${HostIp}" "bash -c '$BashScript'" } Write-Host ">>> 5. Checking deployed revision..." -ForegroundColor Green Start-Sleep -Seconds 2 $Version = Invoke-RestMethod -Uri "https://motorsignal.app/api/version" -Method Get if ($Version.status -ne "ok" -or $Version.service -ne "motorsignal-website") { Write-Error "Website version endpoint returned an unhealthy response." exit 1 } if ($Version.revision -ne $GitSha) { Write-Error "Deployment revision mismatch. Expected $GitSha but production reports $($Version.revision)." exit 1 } Write-Host ">>> Deployed successfully! Verified revision: $($Version.shortRevision)" -ForegroundColor Green