199 lines
7.6 KiB
PowerShell
199 lines
7.6 KiB
PowerShell
# 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.huypham.vn"
|
|
$DomainAdmin = "admin.huypham.vn"
|
|
$DomainUser = "user.huypham.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)) {
|
|
@"
|
|
<!DOCTYPE html><html><head><meta charset="utf-8"><title>SOLUTION ERP</title></head>
|
|
<body style="font:14px sans-serif;padding:40px;text-align:center;color:#555">
|
|
<h1>SOLUTION ERP</h1>
|
|
<p>Site created, waiting for first deploy via Gitea CI/CD.</p>
|
|
</body></html>
|
|
"@ | 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 = @'
|
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
<configuration>
|
|
<system.webServer>
|
|
<rewrite>
|
|
<rules>
|
|
<rule name="HTTP to HTTPS" stopProcessing="true">
|
|
<match url="(.*)" />
|
|
<conditions>
|
|
<add input="{HTTPS}" pattern="^OFF$" />
|
|
</conditions>
|
|
<action type="Redirect" url="https://{HTTP_HOST}/{R:1}" redirectType="Permanent" />
|
|
</rule>
|
|
<rule name="SPA Routes" stopProcessing="true">
|
|
<match url=".*" />
|
|
<conditions logicalGrouping="MatchAll">
|
|
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
|
|
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
|
|
<add input="{REQUEST_URI}" pattern="^/api" negate="true" />
|
|
</conditions>
|
|
<action type="Rewrite" url="/" />
|
|
</rule>
|
|
</rules>
|
|
</rewrite>
|
|
<staticContent>
|
|
<remove fileExtension=".webmanifest" />
|
|
<mimeMap fileExtension=".webmanifest" mimeType="application/manifest+json" />
|
|
</staticContent>
|
|
<httpProtocol>
|
|
<customHeaders>
|
|
<add name="X-Content-Type-Options" value="nosniff" />
|
|
<add name="X-Frame-Options" value="DENY" />
|
|
<add name="Referrer-Policy" value="strict-origin-when-cross-origin" />
|
|
</customHeaders>
|
|
</httpProtocol>
|
|
</system.webServer>
|
|
</configuration>
|
|
'@
|
|
|
|
foreach ($fePath in @($PathAdmin, $PathUser)) {
|
|
Set-Content -Path (Join-Path $fePath "web.config") -Value $spaWebConfig -Encoding UTF8
|
|
}
|
|
Write-Host " Wrote SPA web.config for admin + user"
|
|
|
|
# ===================== 6. Firewall =====================
|
|
Write-Step "Firewall port 80/443"
|
|
$fwExists = Get-NetFirewallRule -DisplayName "HTTP In (SolutionErp)" -ErrorAction SilentlyContinue
|
|
if (-not $fwExists) {
|
|
New-NetFirewallRule -DisplayName "HTTP In (SolutionErp)" -Direction Inbound -LocalPort 80 -Protocol TCP -Action Allow | Out-Null
|
|
New-NetFirewallRule -DisplayName "HTTPS In (SolutionErp)" -Direction Inbound -LocalPort 443 -Protocol TCP -Action Allow | Out-Null
|
|
Write-Host " Created 2 rules"
|
|
} else {
|
|
Write-Host " Exists"
|
|
}
|
|
|
|
# ===================== 7. Recap =====================
|
|
Write-Host ""
|
|
Write-Host "[OK] IIS setup DONE" -ForegroundColor Green
|
|
Write-Host " 3 sites:"
|
|
Write-Host " - $SiteApi -> $DomainApi -> $PathApi (app pool $AppPoolApi)"
|
|
Write-Host " - $SiteAdmin -> $DomainAdmin -> $PathAdmin (no app pool - static)"
|
|
Write-Host " - $SiteUser -> $DomainUser -> $PathUser (no app pool - static)"
|
|
Write-Host ""
|
|
Write-Host " Next:"
|
|
Write-Host " 1. Run .\setup-ssl.ps1 to install HTTPS cert for 3 domains"
|
|
Write-Host " 2. Deploy first build via Gitea Actions (push main to trigger workflow)"
|
|
Write-Host " 3. Verify https://$DomainApi/health/ready -> 200"
|