feat: start-server.ps1 acepta -PythonExe y copys amigables
This commit is contained in:
+167
-58
@@ -1,45 +1,117 @@
|
|||||||
|
param(
|
||||||
|
# Ruta absoluta o nombre del interprete Python que lanza uvicorn.
|
||||||
|
# doctor.ps1 la resuelve porque tras una instalacion fresca el PATH
|
||||||
|
# de esta sesion aun no ve el Python nuevo.
|
||||||
|
[string]$PythonExe = 'python'
|
||||||
|
)
|
||||||
|
|
||||||
$ErrorActionPreference = 'Stop'
|
$ErrorActionPreference = 'Stop'
|
||||||
$root = Split-Path -Parent $PSScriptRoot
|
$root = Split-Path -Parent $PSScriptRoot
|
||||||
Set-Location -LiteralPath $root
|
Set-Location -LiteralPath $root
|
||||||
|
# Force UTF-8 so accented messages render correctly even when launched
|
||||||
|
# from the .bat via `powershell -File` (PS5.1 uses the OEM codepage by default).
|
||||||
|
try { [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 } catch {}
|
||||||
|
|
||||||
# Robust browser opener — tries several Windows methods until one works.
|
# All console output is force-flushed so the user sees live progress even when
|
||||||
|
# the launcher is invoked from a .bat-launched powershell.exe window.
|
||||||
|
function Out-Line($msg, $color = $null) {
|
||||||
|
if ($color) { Write-Host $msg -ForegroundColor $color }
|
||||||
|
else { Write-Host $msg }
|
||||||
|
[Console]::Out.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
# Robust browser opener -- tries several Windows methods until one works.
|
||||||
function Open-Browser($url) {
|
function Open-Browser($url) {
|
||||||
$opened = $false
|
$opened = $false
|
||||||
# Method 1: Start-Process with the URL (default protocol handler)
|
|
||||||
try { Start-Process -FilePath $url; $opened = $true } catch {}
|
try { Start-Process -FilePath $url; $opened = $true } catch {}
|
||||||
if (-not $opened) {
|
if (-not $opened) { try { & explorer.exe $url; $opened = $true } catch {} }
|
||||||
# Method 2: explorer.exe with the URL (opens default browser)
|
if (-not $opened) { try { & cmd.exe /c start "" $url; $opened = $true } catch {} }
|
||||||
try { & explorer.exe $url; $opened = $true } catch {}
|
if ($opened) { Out-Line " [ok] navegador abierto -> $url" DarkGray }
|
||||||
}
|
else { Out-Line " [warn] no se pudo abrir el navegador automaticamente" Yellow }
|
||||||
if (-not $opened) {
|
|
||||||
# Method 3: cmd `start` builtin
|
|
||||||
try { & cmd.exe /c start "" $url; $opened = $true } catch {}
|
|
||||||
}
|
|
||||||
if ($opened) { Write-Host " [ok] abriendo navegador -> $url" -ForegroundColor DarkGray }
|
|
||||||
else { Write-Host " [warn] no se pudo abrir el navegador. Abre manualmente: $url" -ForegroundColor Yellow }
|
|
||||||
return $opened
|
return $opened
|
||||||
}
|
}
|
||||||
|
|
||||||
Write-Host ""
|
# Fast TCP-connect probe. Replaces Invoke-WebRequest (which on Windows
|
||||||
Write-Host " [yt-scraper] iniciando servidor local..." -ForegroundColor Cyan
|
# PowerShell 5.1 ignores -TimeoutSec for the connect phase and can hang
|
||||||
|
# 3-15 s per failed attempt to a closed port -- making a typical port
|
||||||
|
# scan silently take many minutes).
|
||||||
|
function Test-PortOpen($port, $timeoutMs = 350) {
|
||||||
|
$client = New-Object System.Net.Sockets.TcpClient
|
||||||
|
try {
|
||||||
|
$iar = $client.BeginConnect('127.0.0.1', $port, $null, $null)
|
||||||
|
if (-not $iar.AsyncWaitHandle.WaitOne($timeoutMs)) { return $false }
|
||||||
|
$client.EndConnect($iar)
|
||||||
|
return $true
|
||||||
|
} catch { return $false }
|
||||||
|
finally { $client.Close() }
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-ServerHealth($port, $timeoutMs = 800) {
|
||||||
|
try {
|
||||||
|
$r = Invoke-WebRequest -Uri "http://127.0.0.1:$port/healthz" -UseBasicParsing -TimeoutSec ([Math]::Max(1, [int]($timeoutMs/1000)))
|
||||||
|
if ($r.Content -match 'ok') { return $true }
|
||||||
|
} catch {}
|
||||||
|
return $false
|
||||||
|
}
|
||||||
|
|
||||||
|
function Is-PidAlive($processId) {
|
||||||
|
if (-not $processId) { return $false }
|
||||||
|
$p = Get-Process -Id ([int]$processId) -ErrorAction SilentlyContinue
|
||||||
|
return $null -ne $p
|
||||||
|
}
|
||||||
|
|
||||||
|
function Show-State($label, $obj) {
|
||||||
|
Out-Line (" [{0}] puerto={1} pid={2} t={3:o}" -f $label, $obj.port, $obj.pid, (Get-Date))
|
||||||
|
}
|
||||||
|
|
||||||
|
Out-Line ""
|
||||||
|
Out-Line " [yt-scraper] iniciando servidor local..." Cyan
|
||||||
|
|
||||||
if (-not (Test-Path '.run')) { New-Item -ItemType Directory -Path '.run' | Out-Null }
|
if (-not (Test-Path '.run')) { New-Item -ItemType Directory -Path '.run' | Out-Null }
|
||||||
|
|
||||||
# Already running?
|
# --- (1) Check for an already-running server registered in server.info ---
|
||||||
|
# A bound port is ground truth that a server is live. The cmd-wrapper PID
|
||||||
|
# we recorded is a weak signal (cmd /c exits as soon as python takes over),
|
||||||
|
# so we DON'T require the PID to be alive -- only that the port responds.
|
||||||
|
$registeredPort = $null
|
||||||
|
$registeredPid = $null
|
||||||
|
$liveExisting = $false
|
||||||
|
|
||||||
if (Test-Path '.run\server.info') {
|
if (Test-Path '.run\server.info') {
|
||||||
$firstLine = (Get-Content '.run\server.info' -TotalCount 1)
|
$firstLine = (Get-Content '.run\server.info' -TotalCount 1)
|
||||||
$existingPort = ($firstLine -split '\s+')[0]
|
$parts = $firstLine -split '\s+'
|
||||||
try {
|
if ($parts.Count -ge 2) {
|
||||||
$r = Invoke-WebRequest -Uri "http://localhost:$existingPort/healthz" -UseBasicParsing -TimeoutSec 2
|
$registeredPort = $parts[0]
|
||||||
if ($r.Content -match 'ok') {
|
$registeredPid = $parts[1]
|
||||||
Write-Host " [ok] servidor ya activo en el puerto $existingPort" -ForegroundColor Green
|
$portBusy = Test-PortOpen ([int]$registeredPort) 350
|
||||||
Open-Browser "http://127.0.0.1:$existingPort"
|
$pidAlive = Is-PidAlive $registeredPid
|
||||||
exit 0
|
if ($portBusy) {
|
||||||
|
# Port is bound and answering -- this IS the running server.
|
||||||
|
# Even if the recorded cmd-wrapper PID died, python is alive.
|
||||||
|
$liveExisting = $true
|
||||||
|
} else {
|
||||||
|
Out-Line " [info] Habia un registro viejo de otra sesion; limpiandolo..." Yellow
|
||||||
|
Remove-Item '.run\server.info' -Force -ErrorAction SilentlyContinue
|
||||||
|
if (-not $pidAlive) {
|
||||||
|
# also nuke any stray python on that port, just in case
|
||||||
|
Get-NetTCPConnection -State Listen -LocalPort ([int]$registeredPort) -ErrorAction SilentlyContinue |
|
||||||
|
ForEach-Object { try { Stop-Process -Id $_.OwningProcess -Force -ErrorAction SilentlyContinue } catch {} }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch {}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# Collect candidate free ports scanning from 8000.
|
if ($liveExisting) {
|
||||||
|
Out-Line " [ok] servidor ya activo en puerto $registeredPort" Green
|
||||||
|
Out-Line " URL: http://localhost:$registeredPort"
|
||||||
|
Out-Line " log: .run\server.log"
|
||||||
|
Out-Line " stop: stop-server.bat"
|
||||||
|
Open-Browser "http://127.0.0.1:$registeredPort" | Out-Null
|
||||||
|
Out-Line ""
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- (2) Find free ports (fast TcpListener probe, like the original) ---
|
||||||
$candidates = @()
|
$candidates = @()
|
||||||
foreach ($p in 8000..8100) {
|
foreach ($p in 8000..8100) {
|
||||||
try {
|
try {
|
||||||
@@ -50,53 +122,90 @@ foreach ($p in 8000..8100) {
|
|||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
if ($candidates.Count -eq 0) {
|
if ($candidates.Count -eq 0) {
|
||||||
Write-Host " [error] no se encontro ningun puerto libre (8000-8100)" -ForegroundColor Red
|
Out-Line " [error] no se encontro ningun puerto libre (8000-8100)" Red
|
||||||
exit 2
|
exit 2
|
||||||
}
|
}
|
||||||
|
# --- (3) Launch uvicorn on each candidate until one sticks ---
|
||||||
# Launch uvicorn DETACHED in its own persistent minimized console window, logging to .run/server.log.
|
|
||||||
# The `cmd /c` window is an independent console: it survives this launcher exiting.
|
|
||||||
$launched = $false
|
$launched = $false
|
||||||
$proc = $null
|
|
||||||
$port = 0
|
$port = 0
|
||||||
|
$proc = $null
|
||||||
|
|
||||||
foreach ($candidate in $candidates) {
|
foreach ($candidate in $candidates) {
|
||||||
# clear previous log
|
|
||||||
Remove-Item '.run\server.log' -Force -ErrorAction SilentlyContinue
|
Remove-Item '.run\server.log' -Force -ErrorAction SilentlyContinue
|
||||||
$cmdLine = '/c python -m uvicorn yt_scraper.webapp.app:app --host 127.0.0.1 --port ' + $candidate + ' --log-level info > .run\server.log 2>&1'
|
Out-Line " [...] Encendiendo el servidor (intento $candidate)..." Gray
|
||||||
$proc = Start-Process -FilePath 'cmd.exe' -ArgumentList $cmdLine -WorkingDirectory $root -WindowStyle Minimized -PassThru
|
$cmdLine = '/c "' + $PythonExe + '" -m uvicorn yt_scraper.webapp.app:app --host 127.0.0.1 --port ' + $candidate + ' --log-level info > ".run\server.log" 2>&1'
|
||||||
Set-Content -Path '.run\server.info' -Value "$candidate $($proc.Id) $(Get-Date -Format o)" -Encoding ASCII
|
try {
|
||||||
|
$proc = Start-Process -FilePath 'cmd.exe' -ArgumentList $cmdLine -WorkingDirectory $root -WindowStyle Minimized -PassThru
|
||||||
$ok = $false
|
} catch {
|
||||||
for ($i = 0; $i -lt 40; $i++) {
|
Out-Line " [warn] Start-Process fallo en $candidate : $($_.Exception.Message)" Yellow
|
||||||
Start-Sleep -Milliseconds 500
|
continue
|
||||||
try {
|
|
||||||
$r = Invoke-WebRequest -Uri "http://localhost:$candidate/healthz" -UseBasicParsing -TimeoutSec 2
|
|
||||||
if ($r.Content -match 'ok') { $ok = $true; break }
|
|
||||||
} catch {}
|
|
||||||
}
|
}
|
||||||
if ($ok) { $port = $candidate; $launched = $true; break }
|
Out-Line " El servidor corre en segundo plano. Registro: .run\server.log"
|
||||||
|
|
||||||
# failed on this port — show why from the log, then try the next candidate
|
# Fast TCP-poll for the bound port. Bound == uvicorn listening, which
|
||||||
Stop-Process -Id $proc.Id -Force -ErrorAction SilentlyContinue
|
# is all the user needs to know "open this URL in a browser".
|
||||||
Remove-Item '.run\server.info' -Force -ErrorAction SilentlyContinue
|
$boundAt = $null
|
||||||
|
$bound = $false
|
||||||
|
$sw = [System.Diagnostics.Stopwatch]::StartNew()
|
||||||
|
for ($i = 0; $i -lt 50; $i++) {
|
||||||
|
Start-Sleep -Milliseconds 200
|
||||||
|
if (Test-PortOpen $candidate 250) { $bound = $true; $boundAt = $sw.Elapsed; break }
|
||||||
|
# show a tiny heartbeat every ~5 attempts
|
||||||
|
if ($i -in 5,15,30,45) {
|
||||||
|
Out-Line (" ... esperando puerto {0} ({1:0.0}s)" -f $candidate, $sw.Elapsed.TotalSeconds) DarkGray
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not $bound) {
|
||||||
|
Out-Line " [warn] Este intento no respondio; probando el siguiente..." Yellow
|
||||||
|
# Show the actual reason from the log so the user can diagnose.
|
||||||
|
if (Test-Path '.run\server.log') {
|
||||||
|
$tail = Get-Content '.run\server.log' -Tail 6 -ErrorAction SilentlyContinue
|
||||||
|
if ($tail) {
|
||||||
|
Out-Line " (ultimas lineas del log):" DarkGray
|
||||||
|
$tail | ForEach-Object { Out-Line " $_" DarkGray }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try { Stop-Process -Id $proc.Id -Force -ErrorAction SilentlyContinue } catch {}
|
||||||
|
try { Get-NetTCPConnection -State Listen -LocalPort $candidate -ErrorAction SilentlyContinue | ForEach-Object { Stop-Process -Id $_.OwningProcess -Force -ErrorAction SilentlyContinue } } catch {}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
# Port is bound. Reveal the URL RIGHT NOW so the user sees progress.
|
||||||
|
$port = $candidate
|
||||||
|
Set-Content -Path '.run\server.info' -Value "$port $($proc.Id) $(Get-Date -Format o)" -Encoding ASCII
|
||||||
|
|
||||||
|
Out-Line " [ok] Servidor encendido." Green
|
||||||
|
Out-Line " URL: http://localhost:$port"
|
||||||
|
Out-Line " log: .run\server.log"
|
||||||
|
Out-Line " stop: stop-server.bat"
|
||||||
|
Out-Line " Confirmando que todo cargo bien..." DarkGray
|
||||||
|
|
||||||
|
Open-Browser "http://127.0.0.1:$port" | Out-Null
|
||||||
|
|
||||||
|
# Confirm /healthz (separate from TCP-bound so the URL appears first).
|
||||||
|
$healthAt = $null
|
||||||
|
for ($i = 0; $i -lt 25; $i++) {
|
||||||
|
Start-Sleep -Milliseconds 200
|
||||||
|
if (Get-ServerHealth $port 800) { $healthAt = $sw.Elapsed; break }
|
||||||
|
}
|
||||||
|
if ($healthAt) {
|
||||||
|
Out-Line " [ok] /healthz OK (t=$([int]$healthAt.TotalSeconds)s)" Green
|
||||||
|
} else {
|
||||||
|
Out-Line " [warn] El servidor abrio pero tardo en responder; revisa .run\server.log" Yellow
|
||||||
|
}
|
||||||
|
Out-Line ""
|
||||||
|
$launched = $true
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
if (-not $launched) {
|
if (-not $launched) {
|
||||||
Write-Host " [error] no se pudo iniciar el servidor." -ForegroundColor Red
|
Out-Line " [error] no se pudo iniciar el servidor en ninguno de los puertos candidatos." Red
|
||||||
if (Test-Path '.run\server.log') {
|
if (Test-Path '.run\server.log') {
|
||||||
Write-Host " --- ultimo log del servidor ---" -ForegroundColor Yellow
|
Out-Line " --- ultimo log del servidor ---" Yellow
|
||||||
Get-Content '.run\server.log' -Tail 25 | ForEach-Object { Write-Host " $_" }
|
Get-Content '.run\server.log' -Tail 25 | ForEach-Object { Out-Line " $_" }
|
||||||
} else {
|
} else {
|
||||||
Write-Host " instala dependencias: pip install -e `".[web]`"" -ForegroundColor Yellow
|
Out-Line " instala dependencias: pip install -e `".[web]`"" Yellow
|
||||||
}
|
}
|
||||||
exit 3
|
exit 3
|
||||||
}
|
}
|
||||||
|
|
||||||
Open-Browser "http://127.0.0.1:$port"
|
|
||||||
Write-Host ""
|
|
||||||
Write-Host " [ok] servidor activo" -ForegroundColor Green
|
|
||||||
Write-Host " URL: http://localhost:$port"
|
|
||||||
Write-Host " PID: $($proc.Id) (ventana minimizada 'cmd' independiente)"
|
|
||||||
Write-Host " log: .run\server.log"
|
|
||||||
Write-Host " stop: stop-server.bat"
|
|
||||||
Write-Host ""
|
|
||||||
|
|||||||
Reference in New Issue
Block a user