mirror of
https://github.com/rnvm9wjdtj-bot/myaps_api.git
synced 2026-06-02 05:54:40 +00:00
523 lines
18 KiB
PowerShell
523 lines
18 KiB
PowerShell
#Requires -Version 5.1
|
|
<#
|
|
.SYNOPSIS
|
|
FastAPI Server Startup Script
|
|
.DESCRIPTION
|
|
Start FastAPI server with support for both service mode and development mode
|
|
.PARAMETER Port
|
|
Server port number (default: 8000, can be overridden from .env file or command line parameter)
|
|
.PARAMETER Host
|
|
Server host address (default: 0.0.0.0)
|
|
.PARAMETER Mode
|
|
Run mode: "service" for Windows service, "dev" for development (default: "dev")
|
|
.EXAMPLE
|
|
.\run.ps1
|
|
.\run.ps1 -Port 8001
|
|
.\run.ps1 -Mode service
|
|
#>
|
|
[CmdletBinding()]
|
|
param(
|
|
[Parameter(Position=0)]
|
|
[int]$Port = 8000,
|
|
|
|
[Parameter()]
|
|
[string]$HostAddress = "0.0.0.0",
|
|
|
|
[Parameter()]
|
|
[ValidateSet("service", "dev")]
|
|
[string]$Mode = "dev"
|
|
)
|
|
|
|
# Set output encoding to UTF-8
|
|
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
|
$OutputEncoding = [System.Text.Encoding]::UTF8
|
|
|
|
# Get project root directory
|
|
$ProjectRoot = Split-Path -Parent $PSScriptRoot
|
|
|
|
# Set working directory to project root
|
|
Set-Location -Path $ProjectRoot
|
|
if ($Mode -eq "dev") {
|
|
Write-Host "Working directory set to: $ProjectRoot"
|
|
}
|
|
|
|
# Configuration variables
|
|
$App = "main:app"
|
|
$LogFile = "$ProjectRoot\logs\fastapi_server.log"
|
|
$EnvFile = "$ProjectRoot\.env"
|
|
|
|
# Function: Write log
|
|
function Write-Log {
|
|
param([string]$Message)
|
|
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
|
|
$logEntry = "$timestamp - $Message"
|
|
try {
|
|
if (-not (Test-Path "$ProjectRoot\logs")) {
|
|
New-Item -ItemType Directory -Path "$ProjectRoot\logs" -Force | Out-Null
|
|
}
|
|
Add-Content -Path $LogFile -Value $logEntry
|
|
} catch {
|
|
# Ignore logging errors
|
|
}
|
|
}
|
|
|
|
# Function: Load .env file
|
|
function Import-DotEnv {
|
|
param([string]$Path)
|
|
|
|
if (-not (Test-Path $Path)) {
|
|
Write-Log "WARNING: .env file not found at $Path"
|
|
if ($Mode -eq "dev") {
|
|
Write-Host "WARNING: .env file not found at $Path"
|
|
}
|
|
return
|
|
}
|
|
|
|
Write-Log "Loading environment variables from .env file..."
|
|
if ($Mode -eq "dev") {
|
|
Write-Host "Loading environment variables from .env file..."
|
|
}
|
|
|
|
Get-Content $Path | ForEach-Object {
|
|
$line = $_.Trim()
|
|
|
|
# Skip empty lines and comments
|
|
if ([string]::IsNullOrWhiteSpace($line) -or $line.StartsWith("#")) {
|
|
return
|
|
}
|
|
|
|
# Parse KEY=VALUE
|
|
if ($line -match "^([^=]+)=(.*)$") {
|
|
$key = $matches[1].Trim()
|
|
$value = $matches[2].Trim()
|
|
|
|
# Remove possible quotes
|
|
$value = $value -replace "^[`"']|[`"']$"
|
|
|
|
# Set environment variable
|
|
[Environment]::SetEnvironmentVariable($key, $value, "Process")
|
|
}
|
|
}
|
|
}
|
|
|
|
# Function: Load project JSON configuration
|
|
function Import-ProjectConfig {
|
|
param([string]$ProjectDir, [string]$ProjectJson)
|
|
|
|
$configPath = "$ProjectRoot\project_files\$ProjectDir\$ProjectJson.json"
|
|
|
|
if (-not (Test-Path $configPath)) {
|
|
Write-Log "WARNING: Project config file not found at $configPath"
|
|
if ($Mode -eq "dev") {
|
|
Write-Host "WARNING: Project config file not found at $configPath"
|
|
}
|
|
return
|
|
}
|
|
|
|
Write-Log "Loading project configuration from $configPath"
|
|
if ($Mode -eq "dev") {
|
|
Write-Host "Loading project configuration from $configPath"
|
|
}
|
|
|
|
try {
|
|
$config = Get-Content $configPath -Raw | ConvertFrom-Json
|
|
|
|
# Load environment variables from config.env section
|
|
if ($config.env) {
|
|
foreach ($key in $config.env.PSObject.Properties.Name) {
|
|
$value = $config.env.$key
|
|
if ($value -ne $null -and $value -ne "") {
|
|
[Environment]::SetEnvironmentVariable($key, $value, "Process")
|
|
Write-Log "Set environment variable: $key"
|
|
}
|
|
}
|
|
}
|
|
} catch {
|
|
Write-Log "ERROR: Failed to load project config: $_"
|
|
if ($Mode -eq "dev") {
|
|
Write-Host "ERROR: Failed to load project config: $_"
|
|
}
|
|
}
|
|
}
|
|
|
|
# Function: Check if port is in use (dev mode only)
|
|
function Test-PortInUse {
|
|
param([int]$PortNumber)
|
|
|
|
$connections = Get-NetTCPConnection -LocalPort $PortNumber -ErrorAction SilentlyContinue
|
|
if ($connections -eq $null) {
|
|
return $false
|
|
}
|
|
return $connections.Count -gt 0
|
|
}
|
|
|
|
# Function: Get process using port (dev mode only)
|
|
function Get-PortProcess {
|
|
param([int]$PortNumber)
|
|
|
|
$connection = Get-NetTCPConnection -LocalPort $PortNumber -ErrorAction SilentlyContinue | Select-Object -First 1
|
|
if ($connection -ne $null) {
|
|
try {
|
|
return Get-Process -Id $connection.OwningProcess -ErrorAction SilentlyContinue
|
|
} catch {
|
|
return $null
|
|
}
|
|
}
|
|
return $null
|
|
}
|
|
|
|
# Function: Clear port usage (dev mode only)
|
|
function Clear-Port {
|
|
param([int]$PortNumber)
|
|
|
|
if ($Mode -eq "dev") {
|
|
Write-Host "Checking if port $PortNumber is available..."
|
|
}
|
|
|
|
$process = Get-PortProcess -PortNumber $PortNumber
|
|
if ($process) {
|
|
# Skip Idle process (PID 0) as it's a system process and cannot be killed
|
|
if ($process.Id -eq 0) {
|
|
if ($Mode -eq "dev") {
|
|
Write-Host "[INFO] Found Idle process (PID: 0), skipping termination as it's a system process" -ForegroundColor Yellow
|
|
}
|
|
Write-Log "Found Idle process (PID: 0), skipping termination"
|
|
} else {
|
|
if ($Mode -eq "dev") {
|
|
Write-Host "[WARNING] Found process $($process.ProcessName) (PID: $($process.Id)) using port $PortNumber" -ForegroundColor Yellow
|
|
}
|
|
Write-Log "Found process $($process.Id) using port $PortNumber"
|
|
|
|
if ($Mode -eq "dev") {
|
|
Write-Host "Killing process $($process.Id)..."
|
|
}
|
|
try {
|
|
Stop-Process -Id $process.Id -Force -ErrorAction Stop
|
|
Write-Log "Killed process $($process.Id)"
|
|
if ($Mode -eq "dev") {
|
|
Write-Host "[INFO] Process killed successfully" -ForegroundColor Green
|
|
}
|
|
}
|
|
catch {
|
|
if ($Mode -eq "dev") {
|
|
Write-Host "[ERROR] Failed to kill process: $_" -ForegroundColor Red
|
|
}
|
|
Write-Log "Failed to kill process $($process.Id): $_"
|
|
}
|
|
}
|
|
}
|
|
|
|
# Wait for port release
|
|
Start-Sleep -Seconds 5
|
|
|
|
# Verify port is available
|
|
if (Test-PortInUse -PortNumber $PortNumber) {
|
|
# Check if it's still the Idle process
|
|
$process = Get-PortProcess -PortNumber $PortNumber
|
|
if ($process -and $process.Id -eq 0) {
|
|
# Idle process doesn't actually use the port, so consider it available
|
|
if ($Mode -eq "dev") {
|
|
Write-Host "[INFO] Port $PortNumber is available for use (Idle process detected)" -ForegroundColor Green
|
|
}
|
|
Write-Log "Port $PortNumber is available (Idle process detected)"
|
|
return $true
|
|
} else {
|
|
if ($Mode -eq "dev") {
|
|
Write-Host "[ERROR] Port $PortNumber is still in use!" -ForegroundColor Red
|
|
}
|
|
Write-Log "Port $PortNumber is still in use!"
|
|
return $false
|
|
}
|
|
}
|
|
else {
|
|
if ($Mode -eq "dev") {
|
|
Write-Host "[INFO] Port $PortNumber is available for use" -ForegroundColor Green
|
|
}
|
|
Write-Log "Port $PortNumber is available"
|
|
return $true
|
|
}
|
|
}
|
|
|
|
# Function: Find Python interpreter
|
|
function Find-Python {
|
|
$venvPython = "$ProjectRoot\venv\Scripts\python.exe"
|
|
|
|
if (Test-Path $venvPython) {
|
|
if ($Mode -eq "dev") {
|
|
Write-Host "[DEBUG] Virtual Python found: $venvPython"
|
|
}
|
|
return $venvPython
|
|
}
|
|
else {
|
|
if ($Mode -eq "dev") {
|
|
Write-Host "[DEBUG] Virtual Python not found, using system Python"
|
|
}
|
|
$pythonCmd = Get-Command python -ErrorAction SilentlyContinue
|
|
if ($pythonCmd) {
|
|
if ($Mode -eq "dev") {
|
|
Write-Host "[DEBUG] System Python found: $($pythonCmd.Source)"
|
|
}
|
|
return $pythonCmd.Source
|
|
}
|
|
throw "Python not found!"
|
|
}
|
|
}
|
|
|
|
# ==================== Main Program ====================
|
|
|
|
try {
|
|
# Create log directory
|
|
if (-not (Test-Path "$ProjectRoot\logs")) {
|
|
New-Item -ItemType Directory -Path "$ProjectRoot\logs" -Force | Out-Null
|
|
if ($Mode -eq "dev") {
|
|
Write-Host "[INFO] Logs directory created"
|
|
}
|
|
}
|
|
|
|
# Load configuration from .env file (command line parameter has highest priority)
|
|
Import-DotEnv -Path $EnvFile
|
|
|
|
# Load project configuration based on PROJECT_DIR and PROJECT_JSON
|
|
$projectDir = $env:PROJECT_DIR
|
|
$projectJson = $env:PROJECT_JSON
|
|
|
|
if ($projectDir -and $projectJson) {
|
|
Write-Log "Loading project config: $projectDir/$projectJson"
|
|
if ($Mode -eq "dev") {
|
|
Write-Host "Loading project config: $projectDir/$projectJson"
|
|
}
|
|
Import-ProjectConfig -ProjectDir $projectDir -ProjectJson $projectJson
|
|
} else {
|
|
Write-Log "WARNING: PROJECT_DIR or PROJECT_JSON not set in .env file"
|
|
if ($Mode -eq "dev") {
|
|
Write-Host "WARNING: PROJECT_DIR or PROJECT_JSON not set in .env file"
|
|
}
|
|
}
|
|
|
|
# Read default values from environment variables (if not specified in command line)
|
|
if ($PSBoundParameters.ContainsKey("Port") -eq $false -and $env:PORT) {
|
|
$Port = [int]$env:PORT
|
|
}
|
|
if ($PSBoundParameters.ContainsKey("Host") -eq $false -and $env:HOST) {
|
|
$HostAddress = $env:HOST
|
|
}
|
|
|
|
# Set environment variables
|
|
[Environment]::SetEnvironmentVariable("PORT", $Port, "Process")
|
|
[Environment]::SetEnvironmentVariable("HOST", $HostAddress, "Process")
|
|
[Environment]::SetEnvironmentVariable("START_MODE", $Mode, "Process")
|
|
[Environment]::SetEnvironmentVariable("PYTHONPATH", $ProjectRoot, "Process")
|
|
[Environment]::SetEnvironmentVariable("PYTHONUNBUFFERED", "1", "Process")
|
|
|
|
# Check if static directory exists
|
|
$staticDir = "$ProjectRoot\static"
|
|
if (Test-Path $staticDir) {
|
|
Write-Log "Static directory found: $staticDir"
|
|
if ($Mode -eq "dev") {
|
|
Write-Host "Static directory found: $staticDir"
|
|
}
|
|
} else {
|
|
Write-Log "WARNING: Static directory not found at $staticDir"
|
|
if ($Mode -eq "dev") {
|
|
Write-Host "WARNING: Static directory not found at $staticDir"
|
|
}
|
|
}
|
|
|
|
# Display configuration information (dev mode only)
|
|
if ($Mode -eq "dev") {
|
|
Write-Host ""
|
|
Write-Host "FastAPI Server Starting..." -ForegroundColor Cyan
|
|
Write-Host "Port: $Port, Host: $HostAddress"
|
|
Write-Host "Project Root: $ProjectRoot" -ForegroundColor Yellow
|
|
Write-Host "Mode: $Mode"
|
|
Write-Host "Press Ctrl+C to stop gracefully"
|
|
Write-Host ""
|
|
}
|
|
|
|
# Record startup log
|
|
Write-Log "Server starting on port $Port, host $HostAddress, mode $Mode"
|
|
|
|
# Check port availability (dev mode only)
|
|
if ($Mode -eq "dev") {
|
|
Write-Host "[INFO] Checking port availability..."
|
|
Clear-Port -PortNumber $Port
|
|
}
|
|
|
|
# Find Python
|
|
try {
|
|
$Python = Find-Python
|
|
Write-Log "Found Python: $Python"
|
|
if ($Mode -eq "dev") {
|
|
Write-Host "Python: $Python"
|
|
}
|
|
} catch {
|
|
Write-Log "Failed to find Python: $_"
|
|
if ($Mode -eq "dev") {
|
|
Write-Host "[ERROR] Failed to find Python: $_" -ForegroundColor Red
|
|
}
|
|
exit 1
|
|
}
|
|
|
|
# Build uvicorn arguments
|
|
$uvicornArgs = @(
|
|
"-m", "uvicorn",
|
|
$App,
|
|
"--host", $HostAddress,
|
|
"--port", "$Port",
|
|
"--log-level", "info",
|
|
"--access-log"
|
|
)
|
|
|
|
Write-Log "Starting uvicorn server: $Python $($uvicornArgs -join ' '); Working directory: $ProjectRoot"
|
|
if ($Mode -eq "dev") {
|
|
Write-Host ""
|
|
Write-Host "Starting uvicorn server..." -ForegroundColor Cyan
|
|
Write-Host "Uvicorn command: $Python $($uvicornArgs -join ' ')" -ForegroundColor Yellow
|
|
}
|
|
|
|
# Start server
|
|
if ($Mode -eq "service") {
|
|
# Service mode: use Uvicorn on Windows, Gunicorn on Unix
|
|
try {
|
|
# Check if running on Windows
|
|
if ($env:OS -eq "Windows_NT") {
|
|
# On Windows, use Uvicorn directly
|
|
Write-Log "Running on Windows, using Uvicorn single process..."
|
|
if ($Mode -eq "dev") {
|
|
Write-Host "Running on Windows, using Uvicorn single process..." -ForegroundColor Cyan
|
|
}
|
|
& $Python $uvicornArgs
|
|
} else {
|
|
# On Unix/Linux, check if Gunicorn is installed
|
|
$gunicornCheck = & $Python -m pip show gunicorn 2>$null
|
|
if ($gunicornCheck) {
|
|
# Use Gunicorn with multi-process
|
|
Write-Log "Starting Gunicorn server with multiple processes..."
|
|
if ($Mode -eq "dev") {
|
|
Write-Host "Starting Gunicorn server with multiple processes..." -ForegroundColor Cyan
|
|
}
|
|
$gunicornConfig = "$ProjectRoot\scripts\deploy\gunicorn_multiprocess.conf.py"
|
|
& $Python -m gunicorn -c $gunicornConfig main:app
|
|
} else {
|
|
# Fallback to Uvicorn single process
|
|
Write-Log "Gunicorn not found, using Uvicorn single process..."
|
|
if ($Mode -eq "dev") {
|
|
Write-Host "Gunicorn not found, using Uvicorn single process..." -ForegroundColor Yellow
|
|
}
|
|
& $Python $uvicornArgs
|
|
}
|
|
}
|
|
} catch {
|
|
Write-Log "Failed to start server: $_"
|
|
if ($Mode -eq "dev") {
|
|
Write-Host "[ERROR] Failed to start server: $_" -ForegroundColor Red
|
|
}
|
|
exit 1
|
|
}
|
|
} else {
|
|
# Dev mode: run with graceful shutdown support
|
|
try {
|
|
$serverProcess = Start-Process -FilePath $Python -ArgumentList $uvicornArgs -NoNewWindow -PassThru
|
|
if ($Mode -eq "dev") {
|
|
Write-Host "Server process started with PID: $($serverProcess.Id)" -ForegroundColor Green
|
|
}
|
|
Write-Log "Server process started with PID: $($serverProcess.Id)"
|
|
} catch {
|
|
Write-Log "Failed to start server process: $_"
|
|
if ($Mode -eq "dev") {
|
|
Write-Host "[ERROR] Failed to start server process: $_" -ForegroundColor Red
|
|
}
|
|
exit 1
|
|
}
|
|
|
|
# Setup graceful shutdown handler
|
|
$shutdownRequested = $false
|
|
|
|
$null = Register-EngineEvent -SourceIdentifier PowerShell.Exiting -Action {
|
|
$script:shutdownRequested = $true
|
|
}
|
|
|
|
try {
|
|
# Wait for process to exit
|
|
if ($Mode -eq "dev") {
|
|
Write-Host "[DEBUG] Waiting for server process to exit..."
|
|
}
|
|
Wait-Process -Id $serverProcess.Id -ErrorAction Stop
|
|
} catch {
|
|
if ($shutdownRequested -or $Error[0].Exception.Message -match "was not found") {
|
|
if ($Mode -eq "dev") {
|
|
Write-Host ""
|
|
Write-Host "Shutting down server gracefully..." -ForegroundColor Yellow
|
|
}
|
|
Write-Log "Shutting down server gracefully"
|
|
|
|
# Try graceful shutdown first
|
|
$serverProcess.CloseMainWindow() | Out-Null
|
|
Start-Sleep -Seconds 5
|
|
|
|
# Force kill if still running
|
|
if (!$serverProcess.HasExited) {
|
|
if ($Mode -eq "dev") {
|
|
Write-Host "Force killing server process..." -ForegroundColor Yellow
|
|
}
|
|
$serverProcess | Stop-Process -Force -ErrorAction SilentlyContinue
|
|
}
|
|
}
|
|
else {
|
|
if ($Mode -eq "dev") {
|
|
Write-Host ""
|
|
Write-Host "[ERROR] Server process error: $_" -ForegroundColor Red
|
|
}
|
|
Write-Log "Server process error: $_"
|
|
}
|
|
}
|
|
|
|
# Get actual exit code
|
|
$exitCode = $serverProcess.ExitCode
|
|
|
|
# Check exit code
|
|
if ($exitCode -ne 0) {
|
|
if ($Mode -eq "dev") {
|
|
Write-Host ""
|
|
Write-Host "[ERROR] Application failed to start with error code $exitCode" -ForegroundColor Red
|
|
}
|
|
Write-Log "Application failed to start with error code $exitCode"
|
|
exit $exitCode
|
|
}
|
|
}
|
|
} catch {
|
|
Write-Log "Error: $_"
|
|
if ($Mode -eq "dev") {
|
|
Write-Host ""
|
|
Write-Host "[ERROR] An error occurred: $_" -ForegroundColor Red
|
|
}
|
|
exit 1
|
|
} finally {
|
|
# Cleanup work (dev mode only)
|
|
if ($Mode -eq "dev") {
|
|
Write-Host ""
|
|
Write-Host "Server shutting down..." -ForegroundColor Yellow
|
|
}
|
|
Write-Log "Server shutting down"
|
|
|
|
# Clear port usage (dev mode only)
|
|
if ($Mode -eq "dev") {
|
|
Write-Host "Cleaning up any remaining processes on port $Port..."
|
|
$process = Get-PortProcess -PortNumber $Port
|
|
if ($process) {
|
|
try {
|
|
Stop-Process -Id $process.Id -Force -ErrorAction SilentlyContinue
|
|
if ($Mode -eq "dev") {
|
|
Write-Host "Killed process $($process.Id)"
|
|
}
|
|
Write-Log "Killed process $($process.Id) during cleanup"
|
|
} catch {
|
|
# Ignore errors
|
|
}
|
|
}
|
|
|
|
Write-Host "Shutdown complete." -ForegroundColor Green
|
|
}
|
|
Write-Log "Server shutdown complete"
|
|
}
|