# Deploy prebuilt MyOffice artifacts to a Proxmox CT (or any Linux host). # Usage: # .\proxmox\publish.ps1 # .\proxmox\deploy.ps1 -TargetHost 192.168.1.120 # .\proxmox\deploy.ps1 -TargetHost 192.168.1.120 -User root -RemotePath /opt/myoffice/api # # Requires OpenSSH client (ssh/scp). Prefers rsync if available (Git Bash / WSL / cwRsync). # Preserves CT-owned appsettings.Production.json / appsettings.shared.Production.json. [CmdletBinding()] param( [Parameter(Mandatory = $true)] [Alias('ComputerName', 'Host')] [string]$TargetHost, [string]$User = 'root', [Alias('Path')] [string]$RemotePath = '/opt/myoffice/api', [string]$Service = 'myoffice-api', [string]$PublishDir = '', [int]$Port = 22 ) $ErrorActionPreference = 'Stop' $scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path $repoRoot = Resolve-Path (Join-Path $scriptDir '..') if (-not $PublishDir) { $PublishDir = Join-Path $repoRoot '_Published' } if (-not (Test-Path $PublishDir)) { throw "Publish folder not found: $PublishDir — run .\proxmox\publish.ps1 first." } if (-not (Test-Path (Join-Path $PublishDir 'MyOffice.Web.dll'))) { throw "MyOffice.Web.dll missing in $PublishDir — run .\proxmox\publish.ps1 first." } $ctOwnedSettings = @( 'appsettings.Production.json', 'appsettings.shared.Production.json' ) $remote = "${User}@${TargetHost}" $sshTarget = @('-p', "$Port", $remote) $scpTarget = @('-P', "$Port") function Invoke-Remote { param([Parameter(Mandatory = $true)][string]$Command) & ssh @sshTarget $Command if ($LASTEXITCODE -ne 0) { throw "ssh failed (exit $LASTEXITCODE): $Command" } } function Test-Rsync { return [bool](Get-Command rsync -ErrorAction SilentlyContinue) } function ConvertTo-RsyncPath([string]$WindowsPath) { $p = ($WindowsPath.TrimEnd('\', '/') + '/') -replace '\\', '/' if ($p -match '^[A-Za-z]:') { $drive = $p.Substring(0, 1).ToLower() $p = "/$drive" + $p.Substring(2) } return $p } Write-Host "-----------------------------------------------" Write-Host "Deploy $PublishDir -> ${remote}:${RemotePath}" Write-Host "-----------------------------------------------" Write-Host "Stopping $Service..." Invoke-Remote "systemctl stop $Service || true" Invoke-Remote "mkdir -p '$RemotePath'" $rsync = Test-Rsync if ($rsync) { Write-Host "Copying with rsync (preserve CT Production appsettings)..." $rsyncSrc = ConvertTo-RsyncPath $PublishDir $rsyncArgs = @( '-r', '--info=progress2', '--exclude', 'appsettings.Production.json', '--exclude', 'appsettings.shared.Production.json', '-e', "ssh -p $Port", $rsyncSrc, "${remote}:${RemotePath}/" ) & rsync @rsyncArgs if ($LASTEXITCODE -ne 0) { throw "rsync failed with exit code $LASTEXITCODE" } } else { Write-Host "rsync not found — using tar+scp fallback..." $staging = Join-Path $env:TEMP ("myoffice-deploy-" + [guid]::NewGuid().ToString('N')) New-Item -ItemType Directory -Path $staging | Out-Null try { Get-ChildItem -Path $PublishDir -Force | Where-Object { $_.Name -notin $ctOwnedSettings } | ForEach-Object { Copy-Item $_.FullName -Destination $staging -Recurse -Force } $tarLocal = Join-Path $env:TEMP 'myoffice-deploy.tar.gz' if (Test-Path $tarLocal) { Remove-Item -Force $tarLocal } Push-Location $staging try { tar -czf $tarLocal * if ($LASTEXITCODE -ne 0) { throw "tar create failed" } } finally { Pop-Location } & scp @scpTarget $tarLocal "${remote}:/tmp/myoffice-deploy.tar.gz" if ($LASTEXITCODE -ne 0) { throw "scp failed" } Invoke-Remote "tar -xzf /tmp/myoffice-deploy.tar.gz -C '$RemotePath' && rm -f /tmp/myoffice-deploy.tar.gz" } finally { Remove-Item -Recurse -Force $staging -ErrorAction SilentlyContinue Remove-Item -Force (Join-Path $env:TEMP 'myoffice-deploy.tar.gz') -ErrorAction SilentlyContinue } } Write-Host "Starting $Service..." Invoke-Remote "systemctl start $Service" Invoke-Remote "systemctl --no-pager --full status $Service || true" Write-Host "" Write-Host "Deployed to http://${TargetHost}:9100 (or your nginx public URL)" Write-Host ""