Move deployment files to scripts folder for better organization
- Move Deploy-PepApi.ps1 to scripts/ - Move DEPLOY.md to scripts/ - Update all script path references to scripts/Deploy-PepApi.ps1 - Update deployment documentation with correct paths - Add clarification to run from repository root The deploy script already had logic to detect the scripts subfolder, this change improves repository organization by separating deployment tooling from source code. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
280
scripts/DEPLOY.md
Normal file
280
scripts/DEPLOY.md
Normal file
@@ -0,0 +1,280 @@
|
||||
# PepApi Deployment Guide
|
||||
|
||||
## Quick Deployment (Windows Service)
|
||||
|
||||
### Prerequisites
|
||||
- .NET 8 Runtime or SDK installed
|
||||
- Administrator privileges
|
||||
- Network access to PepDB SQL Server
|
||||
- Access to nest files directory
|
||||
|
||||
### Deploy as Windows Service
|
||||
|
||||
```powershell
|
||||
# Run as Administrator from repository root
|
||||
powershell -ExecutionPolicy Bypass -File scripts/Deploy-PepApi.ps1 -OpenFirewall
|
||||
```
|
||||
|
||||
This will:
|
||||
- Build and publish PepApi.Core in Release mode
|
||||
- Install to `C:\Services\PepApi`
|
||||
- Create Windows Service named "PepApi"
|
||||
- Configure service to run on port 8085
|
||||
- Open Windows Firewall for port 8085
|
||||
- Start the service automatically
|
||||
|
||||
### Custom Deployment
|
||||
|
||||
```powershell
|
||||
# Custom service name and location
|
||||
powershell -ExecutionPolicy Bypass -File scripts/Deploy-PepApi.ps1 `
|
||||
-ServiceName "MyPepApi" `
|
||||
-InstallDir "D:\Services\PepApi" `
|
||||
-Urls "http://*:9000" `
|
||||
-OpenFirewall
|
||||
```
|
||||
|
||||
### Configuration
|
||||
|
||||
After deployment, **you must update** `C:\Services\PepApi\appsettings.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"ConnectionStrings": {
|
||||
"PepDB": "data source=YOUR_SERVER\\INSTANCE;initial catalog=PEP;integrated security=True;..."
|
||||
},
|
||||
"PepSettings": {
|
||||
"NestDirectory": "\\\\YOUR_SERVER\\PEP Nest",
|
||||
"MaterialsFile": "C:\\Pep\\PEP2012\\CONFIG\\material.lfn"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Then restart the service:
|
||||
```powershell
|
||||
Restart-Service -Name PepApi
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Alternative: Self-Hosted (No Service)
|
||||
|
||||
### Build and Run
|
||||
|
||||
```bash
|
||||
cd PepApi.Core
|
||||
dotnet publish -c Release -o ./publish
|
||||
cd publish
|
||||
.\PepApi.Core.exe
|
||||
```
|
||||
|
||||
### Run with Custom Settings
|
||||
|
||||
```bash
|
||||
.\PepApi.Core.exe --urls "http://*:8085"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Alternative: IIS Deployment
|
||||
|
||||
### 1. Install Prerequisites
|
||||
- Install ASP.NET Core Hosting Bundle from Microsoft
|
||||
- Create IIS Application Pool (No Managed Code)
|
||||
|
||||
### 2. Publish Application
|
||||
|
||||
```bash
|
||||
dotnet publish PepApi.Core/PepApi.Core.csproj -c Release -o C:\inetpub\PepApi
|
||||
```
|
||||
|
||||
### 3. Configure IIS
|
||||
- Create new website in IIS Manager
|
||||
- Point to `C:\inetpub\PepApi`
|
||||
- Use Application Pool with "No Managed Code"
|
||||
- Set binding to port 8085
|
||||
|
||||
### 4. Update Configuration
|
||||
- Edit `C:\inetpub\PepApi\appsettings.json`
|
||||
- Restart IIS site
|
||||
|
||||
---
|
||||
|
||||
## Verify Deployment
|
||||
|
||||
### Check Service Status
|
||||
```powershell
|
||||
Get-Service -Name PepApi
|
||||
```
|
||||
|
||||
### Test API
|
||||
```powershell
|
||||
# Health check
|
||||
Invoke-WebRequest http://localhost:8085/swagger
|
||||
|
||||
# Test nests endpoint
|
||||
Invoke-WebRequest http://localhost:8085/nests/2024
|
||||
```
|
||||
|
||||
### View Logs
|
||||
```powershell
|
||||
# Service logs in Event Viewer
|
||||
Get-EventLog -LogName Application -Source PepApi -Newest 20
|
||||
|
||||
# Or check console output if running self-hosted
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Service Management
|
||||
|
||||
### Start Service
|
||||
```powershell
|
||||
Start-Service -Name PepApi
|
||||
```
|
||||
|
||||
### Stop Service
|
||||
```powershell
|
||||
Stop-Service -Name PepApi
|
||||
```
|
||||
|
||||
### Restart Service
|
||||
```powershell
|
||||
Restart-Service -Name PepApi
|
||||
```
|
||||
|
||||
### Uninstall Service
|
||||
```powershell
|
||||
Stop-Service -Name PepApi
|
||||
sc.exe delete PepApi
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Firewall Configuration
|
||||
|
||||
If you didn't use `-OpenFirewall` during deployment:
|
||||
|
||||
```powershell
|
||||
# Open port 8085
|
||||
New-NetFirewallRule -DisplayName "PepApi HTTP 8085" `
|
||||
-Direction Inbound `
|
||||
-Protocol TCP `
|
||||
-LocalPort 8085 `
|
||||
-Action Allow
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Service Won't Start
|
||||
|
||||
1. Check Event Viewer for errors:
|
||||
```powershell
|
||||
Get-EventLog -LogName Application -Source PepApi -Newest 10
|
||||
```
|
||||
|
||||
2. Verify configuration:
|
||||
- Database connection string is correct
|
||||
- Network paths are accessible
|
||||
- Files exist at specified paths
|
||||
|
||||
3. Test manually:
|
||||
```bash
|
||||
cd C:\Services\PepApi
|
||||
.\PepApi.Core.exe
|
||||
```
|
||||
|
||||
### Database Connection Errors
|
||||
|
||||
- Verify SQL Server is accessible
|
||||
- Check Windows Authentication permissions
|
||||
- Test connection string with SSMS
|
||||
- Ensure `TrustServerCertificate=True` in connection string
|
||||
|
||||
### File Access Errors
|
||||
|
||||
- Verify service account has read access to:
|
||||
- Nest directory (`\\REMCOSRV0\PEP Nest`)
|
||||
- Materials file (`C:\Pep\PEP2012\CONFIG\material.lfn`)
|
||||
- Configure service to run as appropriate account
|
||||
|
||||
### Port Already in Use
|
||||
|
||||
```powershell
|
||||
# Find what's using port 8085
|
||||
netstat -ano | findstr :8085
|
||||
|
||||
# Kill the process
|
||||
Stop-Process -Id [PID] -Force
|
||||
|
||||
# Or deploy on different port
|
||||
scripts/Deploy-PepApi.ps1 -Urls "http://*:9000"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Update/Redeploy
|
||||
|
||||
To update the service with new code:
|
||||
|
||||
```powershell
|
||||
# Simply run the deploy script again
|
||||
powershell -ExecutionPolicy Bypass -File scripts/Deploy-PepApi.ps1 -OpenFirewall
|
||||
```
|
||||
|
||||
The script will:
|
||||
- Stop the existing service
|
||||
- Deploy the new version
|
||||
- Restart the service
|
||||
|
||||
**Note:** Your `appsettings.json` changes will be preserved.
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
Once deployed, the API will be available at:
|
||||
|
||||
- **Swagger UI:** `http://localhost:8085/swagger`
|
||||
- **OpenAPI Spec:** `http://localhost:8085/swagger/v1/swagger.json`
|
||||
|
||||
### Endpoints
|
||||
- `GET /nests/{year}` - Get nests for year
|
||||
- `GET /nests/{nestName}?year=YYYY` - Get nest (year optional, finds most recent)
|
||||
- `GET /nests/{nestName}/download?year=YYYY` - Download nest file
|
||||
- `GET /nests/{nestName}/plates?year=YYYY` - Get plates
|
||||
- `GET /materials` - Get all materials
|
||||
- `GET /materials/{id}` - Get specific material
|
||||
|
||||
---
|
||||
|
||||
## Security Notes
|
||||
|
||||
⚠️ **Before production deployment:**
|
||||
|
||||
1. Enable HTTPS (not configured by default)
|
||||
2. Add authentication/authorization
|
||||
3. Review CORS policy (currently allows all origins)
|
||||
4. Update DotNetZip package (has security vulnerability)
|
||||
5. Run service with least-privilege account
|
||||
|
||||
---
|
||||
|
||||
## Performance Tips
|
||||
|
||||
- Consider adding response caching
|
||||
- Enable response compression in production
|
||||
- Monitor database query performance
|
||||
- Add connection pooling configuration if needed
|
||||
|
||||
---
|
||||
|
||||
## Support
|
||||
|
||||
For issues:
|
||||
1. Check Event Viewer logs
|
||||
2. Review `appsettings.json` configuration
|
||||
3. Test database and file access manually
|
||||
4. Review MIGRATION_SUMMARY.md for detailed changes
|
||||
188
scripts/Deploy-PepApi.ps1
Normal file
188
scripts/Deploy-PepApi.ps1
Normal file
@@ -0,0 +1,188 @@
|
||||
<#
|
||||
Deploy PepApi as a Windows Service
|
||||
|
||||
Examples:
|
||||
# Run from repository root:
|
||||
powershell -ExecutionPolicy Bypass -File scripts/Deploy-PepApi.ps1 -ServiceName PepApi -InstallDir C:\Services\PepApi -Urls "http://*:8085" -OpenFirewall
|
||||
|
||||
# Custom installation:
|
||||
powershell -ExecutionPolicy Bypass -File scripts/Deploy-PepApi.ps1 -ServiceName PepApiService -InstallDir D:\MyServices\PepApi -Urls "http://*:8085" -OpenFirewall
|
||||
|
||||
Requires: dotnet SDK/runtime installed and administrative privileges.
|
||||
#>
|
||||
|
||||
Param(
|
||||
[string]$ServiceName = "PepApi",
|
||||
[string]$PublishConfiguration = "Release",
|
||||
[string]$InstallDir = "C:\Services\PepApi",
|
||||
[string]$Urls = "http://*:8085",
|
||||
[switch]$OpenFirewall,
|
||||
[int]$PublishTimeoutSeconds = 180,
|
||||
[int]$ServiceStopTimeoutSeconds = 30,
|
||||
[int]$ServiceStartTimeoutSeconds = 30
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
# Detect repository root
|
||||
$ScriptDir = Split-Path -Parent $PSCommandPath
|
||||
$RepoRoot = if ((Split-Path -Leaf $ScriptDir) -eq 'scripts') {
|
||||
Split-Path -Parent $ScriptDir
|
||||
} else {
|
||||
$ScriptDir
|
||||
}
|
||||
|
||||
Write-Host "Repository root: $RepoRoot"
|
||||
$ProjectPath = Join-Path $RepoRoot 'PepApi.Core\PepApi.Core.csproj'
|
||||
|
||||
if (-not (Test-Path -LiteralPath $ProjectPath)) {
|
||||
throw "Project not found at: $ProjectPath"
|
||||
}
|
||||
|
||||
function Ensure-Dir($path) {
|
||||
if (-not (Test-Path -LiteralPath $path)) {
|
||||
New-Item -ItemType Directory -Path $path | Out-Null
|
||||
}
|
||||
}
|
||||
|
||||
function Stop-And-DeleteService($name) {
|
||||
$svc = Get-Service -Name $name -ErrorAction SilentlyContinue
|
||||
if ($null -ne $svc) {
|
||||
if ($svc.Status -ne 'Stopped') {
|
||||
Write-Host "Stopping service '$name'..."
|
||||
Stop-Service -Name $name -Force -ErrorAction SilentlyContinue
|
||||
try { $svc.WaitForStatus('Stopped',[TimeSpan]::FromSeconds($ServiceStopTimeoutSeconds)) | Out-Null } catch {}
|
||||
# If still running, kill by PID
|
||||
$q = & sc.exe queryex $name 2>$null
|
||||
$pidLine = $q | Where-Object { $_ -match 'PID' }
|
||||
if ($pidLine -and ($pidLine -match '(\d+)$')) {
|
||||
$procId = [int]$Matches[1]
|
||||
if ($procId -gt 0) {
|
||||
try { Write-Host "Killing service process PID=$procId ..."; Stop-Process -Id $procId -Force } catch {}
|
||||
}
|
||||
}
|
||||
}
|
||||
Write-Host "Deleting service '$name'..."
|
||||
sc.exe delete $name | Out-Null
|
||||
Start-Sleep -Seconds 1
|
||||
}
|
||||
}
|
||||
|
||||
function Publish-App() {
|
||||
Write-Host "Publishing PepApi.Core to $InstallDir ..."
|
||||
Ensure-Dir $InstallDir
|
||||
|
||||
# Run dotnet publish directly - output will be visible
|
||||
& dotnet publish $ProjectPath -c $PublishConfiguration -o $InstallDir
|
||||
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "dotnet publish failed with exit code $LASTEXITCODE"
|
||||
}
|
||||
|
||||
# Copy appsettings.json if it doesn't exist in install dir (preserve existing config)
|
||||
$sourceSettings = Join-Path (Split-Path -Parent $ProjectPath) 'appsettings.json'
|
||||
$targetSettings = Join-Path $InstallDir 'appsettings.json'
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "============================================"
|
||||
Write-Host "IMPORTANT: Configuration File" -ForegroundColor Yellow
|
||||
Write-Host "============================================"
|
||||
Write-Host "Please review and update the configuration file at:" -ForegroundColor Yellow
|
||||
Write-Host " $targetSettings" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
Write-Host "Update the following settings:" -ForegroundColor Yellow
|
||||
Write-Host " - ConnectionStrings:PepDB (SQL Server connection)" -ForegroundColor White
|
||||
Write-Host " - PepSettings:NestDirectory (network path to nests)" -ForegroundColor White
|
||||
Write-Host " - PepSettings:MaterialsFile (path to material.lfn)" -ForegroundColor White
|
||||
Write-Host "============================================"
|
||||
Write-Host ""
|
||||
}
|
||||
|
||||
function Stop-ExeLocks($path) {
|
||||
$procs = Get-Process -ErrorAction SilentlyContinue | Where-Object {
|
||||
$_.Path -and ($_.Path -ieq $path)
|
||||
}
|
||||
foreach ($p in $procs) {
|
||||
try { Write-Host "Killing process $($p.Id) $($p.ProcessName) ..."; Stop-Process -Id $p.Id -Force } catch {}
|
||||
}
|
||||
# Wait until unlocked
|
||||
for ($i=0; $i -lt 50; $i++) {
|
||||
$still = Get-Process -ErrorAction SilentlyContinue | Where-Object { $_.Path -and ($_.Path -ieq $path) }
|
||||
if (-not $still) { break }
|
||||
Start-Sleep -Milliseconds 200
|
||||
}
|
||||
}
|
||||
|
||||
function Create-Service($name, $bin, $urls) {
|
||||
$binPath = '"' + $bin + '" --urls ' + $urls
|
||||
Write-Host "Creating service '$name' with binPath: $binPath"
|
||||
# Note: space after '=' is required for sc.exe syntax
|
||||
sc.exe create $name binPath= "$binPath" start= auto DisplayName= "$name" | Out-Null
|
||||
# Set recovery to restart on failure
|
||||
sc.exe failure $name reset= 86400 actions= restart/60000/restart/60000/restart/60000 | Out-Null
|
||||
}
|
||||
|
||||
function Start-ServiceSafe($name) {
|
||||
Write-Host "Starting service '$name'..."
|
||||
Start-Service -Name $name
|
||||
(Get-Service -Name $name).WaitForStatus('Running',[TimeSpan]::FromSeconds($ServiceStartTimeoutSeconds)) | Out-Null
|
||||
sc.exe query $name | Write-Host
|
||||
}
|
||||
|
||||
if (-not (Get-Command dotnet -ErrorAction SilentlyContinue)) {
|
||||
throw "dotnet SDK/Runtime not found in PATH. Please install .NET 8+ or add it to PATH."
|
||||
}
|
||||
|
||||
Write-Host "================================================"
|
||||
Write-Host "PepApi Deployment Script" -ForegroundColor Green
|
||||
Write-Host "================================================"
|
||||
Write-Host "Service Name: $ServiceName"
|
||||
Write-Host "Install Dir: $InstallDir"
|
||||
Write-Host "URLs: $Urls"
|
||||
Write-Host "Configuration: $PublishConfiguration"
|
||||
Write-Host "Open Firewall: $OpenFirewall"
|
||||
Write-Host "================================================"
|
||||
Write-Host ""
|
||||
|
||||
Stop-And-DeleteService -name $ServiceName
|
||||
Stop-ExeLocks -path (Join-Path $InstallDir 'PepApi.Core.exe')
|
||||
try { Remove-Item -LiteralPath (Join-Path $InstallDir 'PepApi.Core.exe') -Force -ErrorAction SilentlyContinue } catch {}
|
||||
Publish-App
|
||||
|
||||
$exe = Join-Path $InstallDir 'PepApi.Core.exe'
|
||||
if (-not (Test-Path -LiteralPath $exe)) {
|
||||
throw "Expected published executable not found: $exe"
|
||||
}
|
||||
|
||||
Create-Service -name $ServiceName -bin $exe -urls $Urls
|
||||
|
||||
if ($OpenFirewall) {
|
||||
$port = ($Urls -split ':')[-1]
|
||||
if ($port -match '^(\d+)$') {
|
||||
$ruleName = "$ServiceName HTTP $port"
|
||||
$existingRule = Get-NetFirewallRule -DisplayName $ruleName -ErrorAction SilentlyContinue
|
||||
if ($null -eq $existingRule) {
|
||||
Write-Host "Creating firewall rule for TCP port $port ..."
|
||||
New-NetFirewallRule -DisplayName $ruleName -Direction Inbound -Protocol TCP -LocalPort $port -Action Allow | Out-Null
|
||||
} else {
|
||||
Write-Host "Firewall rule '$ruleName' already exists, skipping creation."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Start-ServiceSafe -name $ServiceName
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "================================================"
|
||||
Write-Host "Deployment Complete!" -ForegroundColor Green
|
||||
Write-Host "================================================"
|
||||
Write-Host "Service '$ServiceName' is running."
|
||||
Write-Host "API available at: $Urls"
|
||||
Write-Host "Swagger UI at: $($Urls -replace '\*', 'localhost')/swagger"
|
||||
Write-Host ""
|
||||
Write-Host "Next Steps:" -ForegroundColor Yellow
|
||||
Write-Host " 1. Verify configuration in: $InstallDir\appsettings.json"
|
||||
Write-Host " 2. Test API endpoint: $($Urls -replace '\*', 'localhost')/swagger"
|
||||
Write-Host " 3. Check service status: Get-Service -Name $ServiceName"
|
||||
Write-Host "================================================"
|
||||
Reference in New Issue
Block a user