Fix tray health detection and harden backend service startup
All checks were successful
CI / test (push) Successful in 7s
All checks were successful
CI / test (push) Successful in 7s
This commit is contained in:
@@ -43,7 +43,38 @@ $publishDir = Join-Path $scriptDir "service_host\publish"
|
||||
$serviceExe = Join-Path $publishDir "ScreenJob.WindowsServiceHost.exe"
|
||||
$logDir = Join-Path $scriptDir "screenjob_runs\service"
|
||||
|
||||
$existingService = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue
|
||||
if ($null -ne $existingService) {
|
||||
if (-not $ForceReinstall) {
|
||||
throw "Service '$ServiceName' already exists. Re-run with -ForceReinstall to replace it."
|
||||
}
|
||||
|
||||
if ($PSCmdlet.ShouldProcess($ServiceName, "Remove existing service")) {
|
||||
if ($existingService.Status -ne "Stopped") {
|
||||
Stop-Service -Name $ServiceName -Force -ErrorAction Stop
|
||||
}
|
||||
|
||||
& sc.exe delete $ServiceName | Out-Null
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "Failed to delete existing service '$ServiceName' (sc.exe exit code $LASTEXITCODE)."
|
||||
}
|
||||
|
||||
$deadline = (Get-Date).AddSeconds(15)
|
||||
while ((Get-Date) -lt $deadline) {
|
||||
$stillThere = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue
|
||||
if ($null -eq $stillThere) {
|
||||
break
|
||||
}
|
||||
Start-Sleep -Milliseconds 300
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($PSCmdlet.ShouldProcess($projectFile, "Publish Windows service host")) {
|
||||
if (Test-Path -LiteralPath $serviceExe) {
|
||||
Remove-Item -LiteralPath $serviceExe -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
& $dotnetCmd.Source publish `
|
||||
$projectFile `
|
||||
-c Release `
|
||||
@@ -63,24 +94,6 @@ if (-not (Test-Path -LiteralPath $serviceExe)) {
|
||||
|
||||
$binaryPath = "`"$serviceExe`" --backend-script `"$backendScript`" --working-dir `"$scriptDir`" --log-dir `"$logDir`""
|
||||
|
||||
$existingService = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue
|
||||
if ($null -ne $existingService) {
|
||||
if (-not $ForceReinstall) {
|
||||
throw "Service '$ServiceName' already exists. Re-run with -ForceReinstall to replace it."
|
||||
}
|
||||
|
||||
if ($PSCmdlet.ShouldProcess($ServiceName, "Remove existing service")) {
|
||||
if ($existingService.Status -ne "Stopped") {
|
||||
Stop-Service -Name $ServiceName -Force -ErrorAction Stop
|
||||
}
|
||||
|
||||
& sc.exe delete $ServiceName | Out-Null
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "Failed to delete existing service '$ServiceName' (sc.exe exit code $LASTEXITCODE)."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($PSCmdlet.ShouldProcess($ServiceName, "Create service")) {
|
||||
New-Service `
|
||||
-Name $ServiceName `
|
||||
|
||||
@@ -28,7 +28,12 @@ function Read-EnvConfig {
|
||||
}
|
||||
$parts = $trimmed.Split("=", 2)
|
||||
if ($parts.Count -eq 2) {
|
||||
$result[$parts[0].Trim()] = $parts[1].Trim()
|
||||
$key = $parts[0].Trim()
|
||||
$value = $parts[1].Trim()
|
||||
if (($value.StartsWith('"') -and $value.EndsWith('"')) -or ($value.StartsWith("'") -and $value.EndsWith("'"))) {
|
||||
$value = $value.Substring(1, $value.Length - 2)
|
||||
}
|
||||
$result[$key] = $value
|
||||
}
|
||||
}
|
||||
return $result
|
||||
@@ -79,17 +84,98 @@ function Get-DashboardUrl {
|
||||
$envFile = Join-Path $scriptDir ".env"
|
||||
$envVars = Read-EnvConfig -EnvFilePath $envFile
|
||||
|
||||
$host = $defaultHost
|
||||
$port = $defaultPort
|
||||
$dashboardHost = $defaultHost
|
||||
$dashboardPort = $defaultPort
|
||||
|
||||
if ($envVars.ContainsKey("SCREENJOB_HOST") -and -not [string]::IsNullOrWhiteSpace($envVars["SCREENJOB_HOST"])) {
|
||||
$host = $envVars["SCREENJOB_HOST"]
|
||||
$dashboardHost = $envVars["SCREENJOB_HOST"]
|
||||
}
|
||||
if ($envVars.ContainsKey("SCREENJOB_PORT") -and -not [string]::IsNullOrWhiteSpace($envVars["SCREENJOB_PORT"])) {
|
||||
$port = $envVars["SCREENJOB_PORT"]
|
||||
$dashboardPort = $envVars["SCREENJOB_PORT"]
|
||||
}
|
||||
|
||||
return "http://{0}:{1}/" -f $host, $port
|
||||
$connectHost = Resolve-ConnectHost -ConfiguredHost $dashboardHost
|
||||
return "http://{0}:{1}/" -f $connectHost, $dashboardPort
|
||||
}
|
||||
|
||||
function Resolve-ConnectHost {
|
||||
param([string]$ConfiguredHost)
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($ConfiguredHost)) {
|
||||
return "127.0.0.1"
|
||||
}
|
||||
|
||||
switch ($ConfiguredHost.Trim().ToLowerInvariant()) {
|
||||
"0.0.0.0" { return "127.0.0.1" }
|
||||
"::" { return "127.0.0.1" }
|
||||
"*" { return "127.0.0.1" }
|
||||
default { return $ConfiguredHost }
|
||||
}
|
||||
}
|
||||
|
||||
function Get-HealthCheckHosts {
|
||||
param([string]$ConfiguredHost)
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($ConfiguredHost)) {
|
||||
return @("127.0.0.1", "localhost")
|
||||
}
|
||||
|
||||
$normalized = $ConfiguredHost.Trim().ToLowerInvariant()
|
||||
switch ($normalized) {
|
||||
"0.0.0.0" { return @("127.0.0.1", "localhost", "::1") }
|
||||
"::" { return @("127.0.0.1", "localhost", "::1") }
|
||||
"*" { return @("127.0.0.1", "localhost", "::1") }
|
||||
default { return @($ConfiguredHost) }
|
||||
}
|
||||
}
|
||||
|
||||
function Test-TcpEndpoint {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$HostName,
|
||||
[Parameter(Mandatory = $true)][int]$Port,
|
||||
[int]$TimeoutMs = 1200
|
||||
)
|
||||
|
||||
$client = New-Object System.Net.Sockets.TcpClient
|
||||
try {
|
||||
$async = $client.BeginConnect($HostName, $Port, $null, $null)
|
||||
$connected = $async.AsyncWaitHandle.WaitOne($TimeoutMs, $false)
|
||||
if (-not $connected) {
|
||||
return $false
|
||||
}
|
||||
$client.EndConnect($async) | Out-Null
|
||||
return $true
|
||||
} catch {
|
||||
return $false
|
||||
} finally {
|
||||
$client.Dispose()
|
||||
}
|
||||
}
|
||||
|
||||
function Get-BackendReachability {
|
||||
$envFile = Join-Path $scriptDir ".env"
|
||||
$envVars = Read-EnvConfig -EnvFilePath $envFile
|
||||
$configuredHost = $defaultHost
|
||||
$configuredPort = $defaultPort
|
||||
|
||||
if ($envVars.ContainsKey("SCREENJOB_HOST") -and -not [string]::IsNullOrWhiteSpace($envVars["SCREENJOB_HOST"])) {
|
||||
$configuredHost = $envVars["SCREENJOB_HOST"]
|
||||
}
|
||||
if ($envVars.ContainsKey("SCREENJOB_PORT") -and -not [string]::IsNullOrWhiteSpace($envVars["SCREENJOB_PORT"])) {
|
||||
$configuredPort = $envVars["SCREENJOB_PORT"]
|
||||
}
|
||||
|
||||
$portNumber = 8787
|
||||
[void][int]::TryParse([string]$configuredPort, [ref]$portNumber)
|
||||
$hostsToTry = Get-HealthCheckHosts -ConfiguredHost $configuredHost
|
||||
|
||||
foreach ($candidateHost in $hostsToTry) {
|
||||
if (Test-TcpEndpoint -HostName $candidateHost -Port $portNumber) {
|
||||
return $true
|
||||
}
|
||||
}
|
||||
|
||||
return $false
|
||||
}
|
||||
|
||||
function Update-TrayState {
|
||||
@@ -100,9 +186,20 @@ function Update-TrayState {
|
||||
)
|
||||
|
||||
$status = Get-ServiceStatusSafe -Name $Name
|
||||
$StatusItem.Text = "Status: $status"
|
||||
$isBackendReachable = Get-BackendReachability
|
||||
|
||||
switch ($status) {
|
||||
$displayStatus = $status
|
||||
if ($status -eq "Running" -and -not $isBackendReachable) {
|
||||
$displayStatus = "Running (Backend Down)"
|
||||
} elseif ($status -eq "Stopped" -and $isBackendReachable) {
|
||||
$displayStatus = "Stopped (Backend Up)"
|
||||
} elseif ($status -eq "NotInstalled" -and $isBackendReachable) {
|
||||
$displayStatus = "NotInstalled (Backend Up)"
|
||||
}
|
||||
|
||||
$StatusItem.Text = "Status: $displayStatus"
|
||||
|
||||
switch ($displayStatus) {
|
||||
"Running" {
|
||||
$NotifyIcon.Icon = [System.Drawing.SystemIcons]::Information
|
||||
}
|
||||
@@ -114,7 +211,7 @@ function Update-TrayState {
|
||||
}
|
||||
}
|
||||
|
||||
$tooltip = "ScreenJob Backend: $status"
|
||||
$tooltip = "ScreenJob Backend: $displayStatus"
|
||||
if ($tooltip.Length -gt 63) {
|
||||
$tooltip = $tooltip.Substring(0, 63)
|
||||
}
|
||||
|
||||
@@ -63,8 +63,12 @@ internal sealed class BackendProcessService : BackgroundService
|
||||
try
|
||||
{
|
||||
await _backendProcess.WaitForExitAsync(stoppingToken);
|
||||
LogStdOut($"Backend process exited with code {_backendProcess.ExitCode}.");
|
||||
_logger.LogWarning("Backend process exited with code {ExitCode}.", _backendProcess.ExitCode);
|
||||
var exitCode = _backendProcess.ExitCode;
|
||||
LogStdErr($"Backend process exited unexpectedly with code {exitCode}.");
|
||||
_logger.LogError("Backend process exited unexpectedly with code {ExitCode}.", exitCode);
|
||||
Environment.ExitCode = exitCode == 0 ? 1 : exitCode;
|
||||
throw new InvalidOperationException(
|
||||
$"Backend process ended unexpectedly. Service host exit code: {Environment.ExitCode}.");
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
|
||||
@@ -15,24 +15,75 @@ function Test-EnvVarLine {
|
||||
return [bool](Select-String -Path $FilePath -Pattern ("^\s*" + [regex]::Escape($Name) + "=") -Quiet)
|
||||
}
|
||||
|
||||
if (-not (Get-Command python -ErrorAction SilentlyContinue)) {
|
||||
function Resolve-PythonExecutable {
|
||||
$venvPython = Join-Path $scriptDir ".venv\Scripts\python.exe"
|
||||
if (-not (Test-Path -LiteralPath $venvPython)) {
|
||||
throw "Python was not found in PATH and .venv\\Scripts\\python.exe is missing. Install Python 3.11+ or create .venv first."
|
||||
if (Test-Path -LiteralPath $venvPython) {
|
||||
return $venvPython
|
||||
}
|
||||
|
||||
$pythonCmd = Get-Command python -ErrorAction SilentlyContinue
|
||||
if ($null -ne $pythonCmd -and (Test-Path -LiteralPath $pythonCmd.Source)) {
|
||||
return $pythonCmd.Source
|
||||
}
|
||||
|
||||
$candidatePyLaunchers = @()
|
||||
$pyFromPath = Get-Command py -ErrorAction SilentlyContinue
|
||||
if ($null -ne $pyFromPath -and (Test-Path -LiteralPath $pyFromPath.Source)) {
|
||||
$candidatePyLaunchers += $pyFromPath.Source
|
||||
}
|
||||
$candidatePyLaunchers += "C:\Windows\py.exe"
|
||||
|
||||
if ($scriptDir -match "^[A-Za-z]:\\Users\\[^\\]+") {
|
||||
$repoUserHome = $Matches[0]
|
||||
$candidatePyLaunchers += (Join-Path $repoUserHome "AppData\Local\Programs\Python\Launcher\py.exe")
|
||||
}
|
||||
|
||||
foreach ($pyLauncher in ($candidatePyLaunchers | Select-Object -Unique)) {
|
||||
if (-not (Test-Path -LiteralPath $pyLauncher)) {
|
||||
continue
|
||||
}
|
||||
try {
|
||||
$resolved = (& $pyLauncher -3 -c "import sys; print(sys.executable)" 2>$null | Select-Object -Last 1).Trim()
|
||||
if ($resolved -and (Test-Path -LiteralPath $resolved)) {
|
||||
return $resolved
|
||||
}
|
||||
} catch {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
$candidatePythonPaths = @()
|
||||
if ($scriptDir -match "^[A-Za-z]:\\Users\\[^\\]+") {
|
||||
$repoUserHome = $Matches[0]
|
||||
$pythonBase = Join-Path $repoUserHome "AppData\Local\Programs\Python"
|
||||
if (Test-Path -LiteralPath $pythonBase) {
|
||||
$candidatePythonPaths += (Get-ChildItem -LiteralPath $pythonBase -Directory -ErrorAction SilentlyContinue |
|
||||
Sort-Object Name -Descending |
|
||||
ForEach-Object { Join-Path $_.FullName "python.exe" })
|
||||
}
|
||||
}
|
||||
|
||||
$candidatePythonPaths += @(
|
||||
"C:\Python314\python.exe",
|
||||
"C:\Python313\python.exe",
|
||||
"C:\Python312\python.exe",
|
||||
"C:\Python311\python.exe",
|
||||
"C:\Program Files\Python314\python.exe",
|
||||
"C:\Program Files\Python313\python.exe",
|
||||
"C:\Program Files\Python312\python.exe",
|
||||
"C:\Program Files\Python311\python.exe"
|
||||
)
|
||||
|
||||
foreach ($candidate in ($candidatePythonPaths | Select-Object -Unique)) {
|
||||
if (Test-Path -LiteralPath $candidate) {
|
||||
return $candidate
|
||||
}
|
||||
}
|
||||
|
||||
throw "Python was not found. Install Python 3.11+ system-wide, or create .venv in the repo root."
|
||||
}
|
||||
|
||||
$pythonExe = $null
|
||||
$venvPython = Join-Path $scriptDir ".venv\Scripts\python.exe"
|
||||
if (Test-Path -LiteralPath $venvPython) {
|
||||
$pythonExe = $venvPython
|
||||
} else {
|
||||
$pythonCmd = Get-Command python -ErrorAction SilentlyContinue
|
||||
if ($null -eq $pythonCmd) {
|
||||
throw "Python was not found in PATH. Install Python 3.11+ or create .venv first."
|
||||
}
|
||||
$pythonExe = $pythonCmd.Source
|
||||
}
|
||||
$pythonExe = Resolve-PythonExecutable
|
||||
|
||||
$envFile = Join-Path $scriptDir ".env"
|
||||
if (-not (Test-Path -LiteralPath $envFile)) {
|
||||
@@ -46,5 +97,5 @@ if (-not (Test-Path -LiteralPath $envFile)) {
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "Starting ScreenJob backend on configured host/port..." -ForegroundColor Cyan
|
||||
Write-Host "Starting ScreenJob backend with Python: $pythonExe" -ForegroundColor Cyan
|
||||
& $pythonExe main.py server
|
||||
|
||||
Reference in New Issue
Block a user