param( [Parameter(Mandatory = $true)] [string]$Version ) $ErrorActionPreference = "Stop" $VpsHost = "bit_bastion" $VpsDeployDir = "/home/stevan/cleancopy.stevanfreeborn.com" $ContainerName = "clean-copy" $ImageName = "clean-copy-server" if ($Version -notmatch '^\d+\.\d+\.\d+$') { Write-Error "Invalid version format '$Version'. Expected semver (e.g. 0.2.0)" exit 1 } Write-Host "=== CleanCopy Release v$Version ===" -ForegroundColor Cyan Write-Host "" try { docker info 2>&1 | Out-Null if ($LASTEXITCODE -ne 0) { throw } } catch { Write-Error "Docker Desktop is not running. Start it and try again." exit 1 } Write-Host "[1/11] Checking SSH connectivity..." -ForegroundColor Yellow try { ssh $VpsHost "echo ok" 2>&1 | Out-Null if ($LASTEXITCODE -ne 0) { throw } } catch { Write-Error "Cannot SSH into $VpsHost. Check your SSH config." exit 1 } Write-Host " OK" -ForegroundColor Green Write-Host "[2/11] Running tests..." -ForegroundColor Yellow cargo test --workspace if ($LASTEXITCODE -ne 0) { Write-Error "Rust tests failed"; exit 1 } Push-Location app npm test if ($LASTEXITCODE -ne 0) { Pop-Location; Write-Error "Frontend tests failed"; exit 1 } Pop-Location Write-Host " All tests passed" -ForegroundColor Green Write-Host "[3/11] Bumping versions to $Version..." -ForegroundColor Yellow $cargoFiles = @( "app/src-tauri/Cargo.toml", "shared/Cargo.toml", "server/Cargo.toml" ) foreach ($file in $cargoFiles) { $content = Get-Content $file -Raw $content = $content -replace '(?m)^version = ".*"', "version = `"$Version`"" Set-Content $file $content -NoNewline } $pkg = Get-Content "app/package.json" -Raw $pkg = $pkg -replace '"version":\s*"[^"]*"', "`"version`": `"$Version`"" Set-Content "app/package.json" $pkg -NoNewline $tauri = Get-Content "app/src-tauri/tauri.conf.json" -Raw $tauri = $tauri -replace '"version":\s*"[^"]*"', "`"version`": `"$Version`"" Set-Content "app/src-tauri/tauri.conf.json" $tauri -NoNewline Write-Host " Updated: Cargo.toml (x3), package.json, tauri.conf.json" -ForegroundColor Green Write-Host "[4/11] Creating git commit and tag..." -ForegroundColor Yellow git add -A git commit -m "release: v$Version" if ($LASTEXITCODE -ne 0) { Write-Error "Git commit failed"; exit 1 } git tag "v$Version" if ($LASTEXITCODE -ne 0) { Write-Error "Git tag failed"; exit 1 } Write-Host " Tagged v$Version" -ForegroundColor Green Write-Host "[5/11] Building Tauri app..." -ForegroundColor Yellow Push-Location app npx tauri build if ($LASTEXITCODE -ne 0) { Pop-Location; Write-Error "Tauri build failed"; exit 1 } Pop-Location Write-Host " Built clean-copy.exe + MSI" -ForegroundColor Green Write-Host "[6/11] Organizing release artifacts..." -ForegroundColor Yellow $releaseDir = "server/releases/$Version/x86_64" New-Item -ItemType Directory -Force -Path $releaseDir | Out-Null $exeSrc = "app/src-tauri/target/release/clean-copy.exe" $msiSrc = Get-ChildItem "app/src-tauri/target/release/bundle/msi/*.msi" | Select-Object -First 1 if (-not (Test-Path $exeSrc)) { Write-Error "Expected $exeSrc not found" exit 1 } Copy-Item $exeSrc "$releaseDir/clean-copy-$Version.exe" Write-Host " Copied clean-copy-$Version.exe" -ForegroundColor Green if ($msiSrc) { Copy-Item $msiSrc.FullName "$releaseDir/$($msiSrc.Name)" Write-Host " Copied $($msiSrc.Name)" -ForegroundColor Green } else { Write-Host " Warning: No MSI found" -ForegroundColor Yellow } Write-Host "[7/11] Building Docker image for linux/arm64..." -ForegroundColor Yellow docker buildx build --platform linux/arm64 -t "${ImageName}:${Version}" -t "${ImageName}:latest" -f server/Dockerfile . --load if ($LASTEXITCODE -ne 0) { Write-Error "Docker build failed"; exit 1 } Write-Host " Built ${ImageName}:${Version}" -ForegroundColor Green Write-Host "[8/11] Saving Docker image to tarball..." -ForegroundColor Yellow $tarball = "${ImageName}-${Version}.tar" docker save "${ImageName}:${Version}" -o $tarball if ($LASTEXITCODE -ne 0) { Write-Error "Docker save failed"; exit 1 } $tarSize = [math]::Round((Get-Item $tarball).Length / 1MB, 1) Write-Host " Saved $tarball ($tarSize MB)" -ForegroundColor Green Write-Host "[9/11] Transferring files to VPS..." -ForegroundColor Yellow scp $tarball "${VpsHost}:${VpsDeployDir}/" if ($LASTEXITCODE -ne 0) { Write-Error "Failed to transfer tarball"; exit 1 } Write-Host " Transferred $tarball" -ForegroundColor Green ssh $VpsHost "mkdir -p ${VpsDeployDir}/data/releases/${Version}/x86_64" scp -r "${releaseDir}/*" "${VpsHost}:${VpsDeployDir}/data/releases/${Version}/x86_64/" if ($LASTEXITCODE -ne 0) { Write-Error "Failed to transfer release artifacts"; exit 1 } Write-Host " Transferred release artifacts" -ForegroundColor Green Write-Host "[10/11] Deploying on VPS..." -ForegroundColor Yellow $deployScript = @" cd ${VpsDeployDir} && \ docker load -i ${ImageName}-${Version}.tar && \ docker stop ${ContainerName} 2>/dev/null; docker rm ${ContainerName} 2>/dev/null; \ docker run -d \ --name ${ContainerName} \ -p 3000:3000 \ -v ${VpsDeployDir}/data:/data \ --env-file ${VpsDeployDir}/.env \ --restart unless-stopped \ ${ImageName}:${Version} && \ rm ${ImageName}-${Version}.tar && \ echo "DEPLOY_OK" "@ $result = ssh $VpsHost $deployScript if ($result -notmatch "DEPLOY_OK") { Write-Error "Deployment failed. Output: $result" exit 1 } Write-Host " Container $ContainerName started" -ForegroundColor Green Write-Host "[11/11] Cleaning up local tarball..." -ForegroundColor Yellow Remove-Item $tarball -Force Write-Host " Removed $tarball" -ForegroundColor Green Write-Host "" Write-Host "=== Release Complete ===" -ForegroundColor Cyan Write-Host " Version: v$Version" Write-Host " Image: ${ImageName}:${Version}" Write-Host " Container: $ContainerName (running)" Write-Host " Deploy dir: ${VpsDeployDir}" Write-Host "" Write-Host " Next: git push && git push --tags" -ForegroundColor Yellow