# Deploy SOLUTION_ERP all-in-one on VPS Windows Server. # Runs 4 steps: SQL -> IIS -> SSL -> Runner. Idempotent. Stops on first step fail. # # Usage: # .\deploy-all.ps1 ` # -SaPassword '' ` # -JwtSecret '<64-char hex>' ` # -VrappPassword '' ` # -RunnerToken '' ` # -AdminEmail 'admin@huypham.vn' # # Prereq (pre-check will verify): # - Windows Server + Admin PowerShell # - IIS + URL Rewrite installed # - SQL Server with login 'sa' + 'vrapp' exists # - .NET 10 Hosting Bundle installed # - Port 80+443 firewall open # - DNS api/admin/eoffice.solutions.com.vn pointing to VPS [CmdletBinding()] param( [Parameter(Mandatory=$true)] [string]$SaPassword, [Parameter(Mandatory=$true)] [string]$JwtSecret, [Parameter(Mandatory=$true)] [string]$VrappPassword, [Parameter(Mandatory=$true)] [string]$RunnerToken, [string]$AdminEmail = "admin@huypham.vn", [switch]$SkipSsl, [switch]$SkipRunner ) $ErrorActionPreference = 'Stop' $ScriptRoot = $PSScriptRoot $StartTime = Get-Date function Write-Banner($text) { Write-Host "" Write-Host ("=" * 70) -ForegroundColor Cyan Write-Host " $text" -ForegroundColor Cyan Write-Host ("=" * 70) -ForegroundColor Cyan } function Test-Prereq { $issues = @() if (-not ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { $issues += "Not running with Admin privilege" } if (-not (Get-Module -ListAvailable -Name SqlServer)) { if (-not (Get-Command sqlcmd -ErrorAction SilentlyContinue)) { $issues += "Neither SqlServer PS module nor sqlcmd.exe available. Install-Module SqlServer -Scope AllUsers -Force" } } if (-not (Get-Module -ListAvailable -Name WebAdministration)) { $issues += "IIS WebAdministration module not installed (Install-WindowsFeature Web-Server,Web-Scripting-Tools)" } $dotnet10 = & dotnet --list-runtimes 2>$null | Select-String "Microsoft.AspNetCore.App 10" if (-not $dotnet10) { $issues += ".NET 10 Hosting Bundle not installed (https://dotnet.microsoft.com/download/dotnet/10.0)" } return $issues } Write-Banner "Pre-check prerequisites" $issues = Test-Prereq if ($issues.Count -gt 0) { Write-Host "[FAIL] Issues:" -ForegroundColor Red $issues | ForEach-Object { Write-Host " - $_" -ForegroundColor Red } Write-Host "" Write-Host "Fix the above then re-run." -ForegroundColor Yellow exit 1 } Write-Host "[OK] All prerequisites present" # ===================== Step 1: SQL DB ===================== Write-Banner "Step 1/4: SQL Database setup" try { & "$ScriptRoot\setup-sql-db.ps1" -SaPassword $SaPassword if ($LASTEXITCODE -ne 0) { throw "setup-sql-db.ps1 exit $LASTEXITCODE" } } catch { Write-Host "[FAIL] Step 1: $_" -ForegroundColor Red exit 1 } # ===================== Step 2: IIS sites ===================== Write-Banner "Step 2/4: IIS sites + app pool" try { & "$ScriptRoot\setup-iis-sites.ps1" if ($LASTEXITCODE -ne 0) { throw "setup-iis-sites.ps1 exit $LASTEXITCODE" } } catch { Write-Host "[FAIL] Step 2: $_" -ForegroundColor Red exit 1 } # ===================== Step 3: appsettings.Production.json ===================== Write-Banner "Step 3/4: Write appsettings.Production.json" $apiPath = "C:\inetpub\solution-erp\api" $example = "$apiPath\appsettings.Production.json.example" $prod = "$apiPath\appsettings.Production.json" if (Test-Path $example) { $content = (Get-Content $example -Raw) ` -replace [regex]::Escape('__SET_VIA_SECRETS__'), $VrappPassword ` -replace [regex]::Escape('__SET_VIA_USER_SECRETS_OR_ENV__minimum_64_chars_random'), $JwtSecret Set-Content -Path $prod -Value $content -Encoding UTF8 Write-Host "[OK] Wrote $prod" # ACL: only Administrators + app pool identity can read icacls $prod /inheritance:r | Out-Null icacls $prod /grant:r 'Administrators:(R,W)' 'IIS AppPool\SolutionErp-Api:(R)' | Out-Null Write-Host " ACL restricted (Admins RW + AppPool R only)" } else { Write-Warning "Template not found at $example - skip (deploy may not have run yet; need Gitea Actions deploy first)" Write-Warning "After CI deploy, re-run this step or copy manually." } # ===================== Step 4: SSL ===================== if (-not $SkipSsl) { Write-Banner "Step 4/4 (SSL): win-acme Let's Encrypt" Write-Host "WARNING: Port 80 must be reachable from Internet (Let's Encrypt HTTP-01)" Write-Host " Test from outside: curl http://api.solutions.com.vn" $confirm = Read-Host "Continue? (y/N)" if ($confirm -eq 'y') { try { & "$ScriptRoot\setup-ssl.ps1" } catch { Write-Warning "SSL fail: $_ - can fix later + re-run setup-ssl.ps1" } } else { Write-Host "Skip SSL - run setup-ssl.ps1 manually later" } } else { Write-Host "Skip SSL (-SkipSsl flag)" } # ===================== Step 5: Runner ===================== if (-not $SkipRunner -and $RunnerToken -ne "SKIP") { Write-Banner "Step 5 (Runner): Gitea Actions runner" try { & "$ScriptRoot\setup-gitea-runner.ps1" -RegistrationToken $RunnerToken } catch { Write-Warning "Runner setup fail: $_ - run setup-gitea-runner.ps1 manually later" } } # ===================== Summary ===================== $duration = (Get-Date) - $StartTime Write-Banner "[OK] DEPLOY ALL DONE - $([int]$duration.TotalMinutes)m $([int]($duration.TotalSeconds % 60))s" Write-Host "" Write-Host "Next:" Write-Host " 1. Verify 3 domains:" Write-Host " curl https://api.solutions.com.vn/health/live" Write-Host " curl -I https://admin.solutions.com.vn" Write-Host " curl -I https://eoffice.solutions.com.vn" Write-Host "" Write-Host " 2. Set remaining 2 Gitea secrets (if not done):" Write-Host " https://git.baocaogiaoduc.vn/vietreport-admin/solution-erp/settings/actions/secrets" Write-Host " - JWT_SECRET (update with 64-char)" Write-Host " - IIS_PASSWORD (Windows admin password)" Write-Host "" Write-Host " 3. Trigger deploy: push main commit -> Gitea Actions pick up -> workflow runs" Write-Host "" Write-Host " 4. Change default admin password after login (security-checklist.md)"