# Setup IIS sites + app pools for SOLUTION_ERP on VPS Windows Server. # Shared with VIETREPORT - naming isolation: SolutionErp-* prefix to avoid conflict. # Run on VPS with admin privilege. Idempotent. # # Usage: # .\setup-iis-sites.ps1 # # Prereq: # - IIS installed + features: Application Initialization, URL Rewrite, ARR (optional) # - .NET 10 Hosting Bundle installed # - Port 80/443 firewall open $ErrorActionPreference = 'Stop' Import-Module WebAdministration # ===================== Config ===================== $Root = "C:\inetpub\solution-erp" $PathApi = "$Root\api" $PathAdmin = "$Root\fe-admin" $PathUser = "$Root\fe-user" $PathLogs = "$Root\logs" $PathUploads = "$Root\uploads" $PathTemplates = "$Root\api\wwwroot\templates" $AppPoolApi = "SolutionErp-Api" $DomainApi = "api.solutions.com.vn" $DomainAdmin = "admin.solutions.com.vn" $DomainUser = "eoffice.solutions.com.vn" $SiteApi = "SolutionErp-Api" $SiteAdmin = "SolutionErp-Admin" $SiteUser = "SolutionErp-User" function Write-Step($msg) { Write-Host "==> $msg" -ForegroundColor Cyan } # ===================== 1. Directories ===================== Write-Step "Create directories under $Root" foreach ($p in @($Root, $PathApi, $PathAdmin, $PathUser, $PathLogs, $PathUploads, $PathTemplates)) { if (-not (Test-Path $p)) { New-Item -ItemType Directory -Force -Path $p | Out-Null Write-Host " Created $p" } else { Write-Host " Exists $p" } } # Grant app pool identity write permission $rule = New-Object System.Security.AccessControl.FileSystemAccessRule( "IIS AppPool\$AppPoolApi", "Modify", "ContainerInherit,ObjectInherit", "None", "Allow") try { $acl = Get-Acl $Root $acl.SetAccessRule($rule) Set-Acl -Path $Root -AclObject $acl } catch { Write-Warning " ACL set may fail if app pool does not exist yet - will retry" } # ===================== 2. App pool (Api only, FE is static) ===================== Write-Step "App pool: $AppPoolApi" if (-not (Test-Path "IIS:\AppPools\$AppPoolApi")) { New-WebAppPool -Name $AppPoolApi | Out-Null Write-Host " Created" } Set-ItemProperty "IIS:\AppPools\$AppPoolApi" -Name managedRuntimeVersion -Value "" Set-ItemProperty "IIS:\AppPools\$AppPoolApi" -Name startMode -Value "AlwaysRunning" Set-ItemProperty "IIS:\AppPools\$AppPoolApi" -Name processModel.identityType -Value "ApplicationPoolIdentity" Set-ItemProperty "IIS:\AppPools\$AppPoolApi" -Name processModel.idleTimeout -Value "00:00:00" Set-ItemProperty "IIS:\AppPools\$AppPoolApi" -Name recycling.periodicRestart.time -Value "00:00:00" Write-Host " Configured (NoManagedCode + AlwaysRunning + no idle timeout + no daily recycle)" # Re-grant ACL after pool created (identity must exist) try { $acl = Get-Acl $Root $acl.SetAccessRule($rule) Set-Acl -Path $Root -AclObject $acl Write-Host " ACL granted Modify to IIS AppPool\$AppPoolApi" } catch { Write-Warning " ACL fail: $_" } # ===================== 3. Sites ===================== function Ensure-Site { param( [string]$Name, [string]$HostName, [string]$PhysicalPath, [string]$AppPool ) Write-Step "Site: $Name ($HostName)" if (-not (Test-Path "IIS:\Sites\$Name")) { # Port 80 HTTP - SSL added later via win-acme $params = @{ Name = $Name HostHeader = $HostName PhysicalPath = $PhysicalPath Port = 80 } if ($AppPool) { $params.ApplicationPool = $AppPool } New-WebSite @params | Out-Null Write-Host " Created" } else { Write-Host " Exists" Set-ItemProperty "IIS:\Sites\$Name" -Name physicalPath -Value $PhysicalPath if ($AppPool) { Set-ItemProperty "IIS:\Sites\$Name" -Name applicationPool -Value $AppPool } } } Ensure-Site -Name $SiteApi -HostName $DomainApi -PhysicalPath $PathApi -AppPool $AppPoolApi Ensure-Site -Name $SiteAdmin -HostName $DomainAdmin -PhysicalPath $PathAdmin -AppPool "" Ensure-Site -Name $SiteUser -HostName $DomainUser -PhysicalPath $PathUser -AppPool "" # ===================== 4. Placeholder index.html for FE (temporary until first real deploy) ===================== Write-Step "Placeholder index.html (pre-deploy)" foreach ($fePath in @($PathAdmin, $PathUser)) { $idx = Join-Path $fePath "index.html" if (-not (Test-Path $idx)) { @"
Site created, waiting for first deploy via Gitea CI/CD.
"@ | Set-Content -Path $idx -Encoding UTF8 Write-Host " Wrote $idx" } else { Write-Host " Exists $idx" } } # ===================== 5. web.config for FE SPA routing ===================== Write-Step "web.config for FE SPA fallback" $spaWebConfig = @'