param( [ValidateSet("start", "stop", "restart", "status", "logs")] [string]$Action = "status", [int]$Port = 8080 ) $ScriptRoot = Split-Path -Parent $MyInvocation.MyCommand.Path $ProjectRoot = Split-Path -Parent $ScriptRoot $RuntimeRoot = Join-Path $ProjectRoot "runtime" $LogRoot = Join-Path $RuntimeRoot "logs" $RunRoot = Join-Path $RuntimeRoot "run" $BinRoot = Join-Path $RuntimeRoot "bin" $CacheRoot = Join-Path $RuntimeRoot "cache" $PidFile = Join-Path $RunRoot "teraclone.pid" $StdOutLogFile = Join-Path $LogRoot "server.out.log" $StdErrLogFile = Join-Path $LogRoot "server.err.log" $DevExe = Join-Path $BinRoot "teraclone-dev.exe" $GoCache = Join-Path $CacheRoot "go-build" $GoTmp = Join-Path $CacheRoot "go-tmp" $Port = [string]$Port $StdOutEventSource = "teraclone.stdout.$Port" $StdErrEventSource = "teraclone.stderr.$Port" function Ensure-RuntimeLayout { New-Item -ItemType Directory -Force -Path $LogRoot | Out-Null New-Item -ItemType Directory -Force -Path $RunRoot | Out-Null New-Item -ItemType Directory -Force -Path $BinRoot | Out-Null New-Item -ItemType Directory -Force -Path $GoCache | Out-Null New-Item -ItemType Directory -Force -Path $GoTmp | Out-Null } function Get-PortOwnerPid { $netstatLines = netstat -ano -p TCP | Select-String ":$Port\s+.*LISTENING\s+(\d+)$" if ($null -eq $netstatLines) { return $null } foreach ($line in $netstatLines) { if ($line.Matches.Count -gt 0) { return [int]$line.Matches[0].Groups[1].Value } } return $null } function Get-PortOwnerProcess { $ownerPid = Get-PortOwnerPid if ($null -eq $ownerPid) { return $null } return Get-Process -Id $ownerPid -ErrorAction SilentlyContinue } function Get-ServerProcess { if (-not (Test-Path $PidFile)) { return $null } $pidLine = Get-Content $PidFile -ErrorAction SilentlyContinue | Select-Object -First 1 if ($null -eq $pidLine) { Remove-Item $PidFile -ErrorAction SilentlyContinue return $null } $pidValue = $pidLine.Trim() if ([string]::IsNullOrWhiteSpace($pidValue)) { Remove-Item $PidFile -ErrorAction SilentlyContinue return $null } $process = Get-Process -Id $pidValue -ErrorAction SilentlyContinue if ($null -eq $process) { Remove-Item $PidFile -ErrorAction SilentlyContinue return $null } return $process } function Clear-LogEventSubscriptions { foreach ($source in @($StdOutEventSource, $StdErrEventSource)) { Unregister-Event -SourceIdentifier $source -ErrorAction SilentlyContinue Get-Job -ErrorAction SilentlyContinue | Where-Object { $_.Name -eq $source } | Remove-Job -Force -ErrorAction SilentlyContinue } } function Start-Server { Ensure-RuntimeLayout Clear-LogEventSubscriptions $existing = Get-ServerProcess if ($null -ne $existing) { Write-Host "Server already running. PID: $($existing.Id)" return } $portOwner = Get-PortOwnerProcess if ($null -ne $portOwner) { Write-Host "Port $Port is already in use by PID $($portOwner.Id) ($($portOwner.ProcessName))." Write-Host "Run '.\\scripts\\dev.ps1 stop' first, or free the port manually." return } if (Test-Path $StdOutLogFile) { Remove-Item $StdOutLogFile -ErrorAction SilentlyContinue } if (Test-Path $StdErrLogFile) { Remove-Item $StdErrLogFile -ErrorAction SilentlyContinue } if (Test-Path $DevExe) { Remove-Item $DevExe -ErrorAction SilentlyContinue } $env:GOCACHE = $GoCache $env:GOTMPDIR = $GoTmp & go build -buildvcs=false -o $DevExe ./cmd/server if ($LASTEXITCODE -ne 0 -or -not (Test-Path $DevExe)) { Write-Host "Build failed." return } $startInfo = New-Object System.Diagnostics.ProcessStartInfo $startInfo.FileName = $DevExe $startInfo.WorkingDirectory = $ProjectRoot $startInfo.UseShellExecute = $false $startInfo.CreateNoWindow = $true $startInfo.RedirectStandardOutput = $true $startInfo.RedirectStandardError = $true $startInfo.EnvironmentVariables["PORT"] = $Port $process = New-Object System.Diagnostics.Process $process.StartInfo = $startInfo if (-not $process.Start()) { Write-Host "Server failed to start." return } Register-ObjectEvent -InputObject $process -EventName OutputDataReceived -SourceIdentifier $StdOutEventSource -Action { if ($EventArgs.Data -ne $null) { [System.IO.File]::AppendAllText($Event.MessageData.Path, $EventArgs.Data + [Environment]::NewLine) } } -MessageData @{ Path = $StdOutLogFile } | Out-Null Register-ObjectEvent -InputObject $process -EventName ErrorDataReceived -SourceIdentifier $StdErrEventSource -Action { if ($EventArgs.Data -ne $null) { [System.IO.File]::AppendAllText($Event.MessageData.Path, $EventArgs.Data + [Environment]::NewLine) } } -MessageData @{ Path = $StdErrLogFile } | Out-Null $process.BeginOutputReadLine() $process.BeginErrorReadLine() Set-Content -Path $PidFile -Value $process.Id Start-Sleep -Seconds 2 $running = Get-ServerProcess $portOwnerAfterStart = Get-PortOwnerProcess if ($null -eq $running -or $null -eq $portOwnerAfterStart) { Write-Host "Server failed to start." if (Test-Path $StdOutLogFile) { Get-Content $StdOutLogFile } if (Test-Path $StdErrLogFile) { Get-Content $StdErrLogFile } return } Write-Host "Server started." Write-Host "PID: $($portOwnerAfterStart.Id)" Write-Host "URL: http://localhost:$Port" } function Stop-Server { Clear-LogEventSubscriptions $existing = Get-ServerProcess if ($null -ne $existing) { Stop-Process -Id $existing.Id -Force -ErrorAction SilentlyContinue Remove-Item $PidFile -ErrorAction SilentlyContinue Start-Sleep -Milliseconds 500 } $portOwner = Get-PortOwnerProcess if ($null -ne $portOwner) { Stop-Process -Id $portOwner.Id -Force -ErrorAction SilentlyContinue if (Test-Path $DevExe) { Remove-Item $DevExe -ErrorAction SilentlyContinue } Write-Host "Server stopped. PID: $($portOwner.Id)" return } if (Test-Path $DevExe) { Remove-Item $DevExe -ErrorAction SilentlyContinue } Write-Host "Server is not running." } function Restart-Server { Stop-Server Start-Server } function Show-Status { $portOwner = Get-PortOwnerProcess if ($null -eq $portOwner) { Write-Host "Server status: stopped" return } Write-Host "Server status: running" Write-Host "PID: $($portOwner.Id)" Write-Host "Process: $($portOwner.ProcessName)" Write-Host "URL: http://localhost:$Port" } function Show-Logs { $hasStdOut = Test-Path $StdOutLogFile $hasStdErr = Test-Path $StdErrLogFile if (-not $hasStdOut -and -not $hasStdErr) { Write-Host "No log file yet." return } if ($hasStdOut) { Write-Host "[stdout]" Get-Content $StdOutLogFile } if ($hasStdErr) { Write-Host "[stderr]" Get-Content $StdErrLogFile } } switch ($Action) { "start" { Start-Server } "stop" { Stop-Server } "restart" { Restart-Server } "status" { Show-Status } "logs" { Show-Logs } }