Use selected port for post-install browser watcher
This commit is contained in:
parent
b9f50ed00c
commit
b869eabe07
3 changed files with 101 additions and 11 deletions
43
install.ps1
43
install.ps1
|
|
@ -2688,10 +2688,42 @@ exit 0
|
|||
# caller explicitly disabled the post-install prompt.
|
||||
# In non-interactive environments (CI, Docker) just print instructions.
|
||||
$IsInteractive = (-not $SkipAutostart) -and [Environment]::UserInteractive -and (-not [Console]::IsInputRedirected)
|
||||
# Select the same bounded free-port range as the generated desktop launcher.
|
||||
# Passing the selected port to both the server and watcher prevents an
|
||||
# existing Studio on 8888 from moving the backend while the watcher stays
|
||||
# behind.
|
||||
function Find-PostInstallStudioPort {
|
||||
param([int]$BasePort = 8888, [int]$MaxPortOffset = 20)
|
||||
$probes = @(
|
||||
@{ Family = [System.Net.Sockets.AddressFamily]::InterNetwork; Host = '127.0.0.1' },
|
||||
@{ Family = [System.Net.Sockets.AddressFamily]::InterNetworkV6; Host = '::1' }
|
||||
)
|
||||
for ($offset = 0; $offset -le $MaxPortOffset; $offset++) {
|
||||
$candidate = $BasePort + $offset
|
||||
$busy = $false
|
||||
foreach ($probe in $probes) {
|
||||
$client = $null
|
||||
try {
|
||||
$client = [System.Net.Sockets.TcpClient]::new($probe.Family)
|
||||
$connect = $client.ConnectAsync($probe.Host, $candidate)
|
||||
if ($connect.Wait(100) -and $client.Connected) {
|
||||
$busy = $true
|
||||
break
|
||||
}
|
||||
} catch {
|
||||
} finally {
|
||||
if ($client) { $client.Dispose() }
|
||||
}
|
||||
}
|
||||
if (-not $busy) { return $candidate }
|
||||
}
|
||||
return $BasePort
|
||||
}
|
||||
|
||||
# Background watcher for the foreground launch below: once the server is
|
||||
# healthy, open the browser per the persisted preference (mirrors the
|
||||
# desktop launcher). Guarded by the per-install root id so a different
|
||||
# Studio already on the port is never the one opened.
|
||||
# healthy on the selected port, open the browser per the persisted
|
||||
# preference. Guarded by the per-install root id so a different Studio is
|
||||
# never the one opened.
|
||||
$_browserWatch = {
|
||||
param($RootId, $Port)
|
||||
$deadline = (Get-Date).AddSeconds(120)
|
||||
|
|
@ -2711,6 +2743,7 @@ exit 0
|
|||
Write-Host ""
|
||||
$reply = Read-Host " Start Unsloth Studio now? [Y/n]"
|
||||
if ([string]::IsNullOrWhiteSpace($reply) -or $reply -match '^[Yy]') {
|
||||
$_launchPort = Find-PostInstallStudioPort
|
||||
# Open the browser once the server is up, unless opted out. The
|
||||
# server prints its own URL, so no watcher is needed when off.
|
||||
if ($OpenBrowserPref -ne '0') {
|
||||
|
|
@ -2720,10 +2753,10 @@ exit 0
|
|||
try { $_watchRootId = ([System.IO.File]::ReadAllText($_watchIdFile)).Trim() } catch {}
|
||||
}
|
||||
try {
|
||||
$null = Start-Job -ScriptBlock $_browserWatch -ArgumentList @($_watchRootId, 8888)
|
||||
$null = Start-Job -ScriptBlock $_browserWatch -ArgumentList @($_watchRootId, $_launchPort)
|
||||
} catch {}
|
||||
}
|
||||
& $UnslothExe studio -p 8888
|
||||
& $UnslothExe studio -p $_launchPort
|
||||
} else {
|
||||
step "launch" "to start later, run:"
|
||||
substep "unsloth studio -p 8888"
|
||||
|
|
|
|||
52
install.sh
52
install.sh
|
|
@ -3295,10 +3295,48 @@ printf " ${C_TITLE}%s${C_RST}\n" "Unsloth Studio installed!"
|
|||
printf " ${C_DIM}%s${C_RST}\n" "$RULE"
|
||||
echo ""
|
||||
|
||||
# Select the same bounded free-port range as the generated desktop launcher.
|
||||
# Passing the selected port to both the server and watcher prevents an existing
|
||||
# Studio on 8888 from making the backend move while the watcher stays behind.
|
||||
_find_post_install_port() {
|
||||
_pifp_base="${1:-8888}"
|
||||
_pifp_max_offset="${2:-20}"
|
||||
"$VENV_DIR/bin/python" - "$_pifp_base" "$_pifp_max_offset" <<'PY'
|
||||
import socket
|
||||
import sys
|
||||
|
||||
base = int(sys.argv[1])
|
||||
max_offset = int(sys.argv[2])
|
||||
|
||||
def is_free(port):
|
||||
endpoints = (
|
||||
(socket.AF_INET, ("127.0.0.1", port)),
|
||||
(socket.AF_INET6, ("::1", port, 0, 0)),
|
||||
)
|
||||
for family, address in endpoints:
|
||||
try:
|
||||
with socket.socket(family, socket.SOCK_STREAM) as probe:
|
||||
probe.settimeout(0.1)
|
||||
if probe.connect_ex(address) == 0:
|
||||
return False
|
||||
except OSError:
|
||||
# IPv6 can be unavailable; match the backend's loopback probe.
|
||||
continue
|
||||
return True
|
||||
|
||||
for offset in range(max_offset + 1):
|
||||
candidate = base + offset
|
||||
if is_free(candidate):
|
||||
print(candidate)
|
||||
raise SystemExit(0)
|
||||
raise SystemExit(1)
|
||||
PY
|
||||
}
|
||||
|
||||
# Background watcher for the post-install foreground launch below: once the
|
||||
# server is healthy, open the browser per the persisted preference (mirrors
|
||||
# the desktop launcher). Guarded by the per-install root id so a different
|
||||
# Studio already on the port is never the one opened.
|
||||
# server is healthy on the selected port, open the browser per the persisted
|
||||
# preference. Guarded by the per-install root id so a different Studio is never
|
||||
# the one opened.
|
||||
_post_install_browser_watch() {
|
||||
_pibw_port="$1"
|
||||
_pibw_url="http://localhost:$_pibw_port"
|
||||
|
|
@ -3353,10 +3391,14 @@ if [ "$_SKIP_AUTOSTART" != true ] && [ -t 1 ]; then
|
|||
case "${_reply:-y}" in
|
||||
[Yy]*|"")
|
||||
step "launch" "starting Unsloth Studio..."
|
||||
_post_install_port=$(_find_post_install_port 8888 20) || _post_install_port=8888
|
||||
case "$_post_install_port" in
|
||||
''|*[!0-9]*) _post_install_port=8888 ;;
|
||||
esac
|
||||
# Open the browser once the server is up, unless opted out. The
|
||||
# server prints its own URL, so no watcher is needed when off.
|
||||
if [ "${_STUDIO_OPEN_BROWSER:-1}" != "0" ]; then
|
||||
_post_install_browser_watch 8888
|
||||
_post_install_browser_watch "$_post_install_port"
|
||||
fi
|
||||
# Detach stdin from the `curl | sh` pipe: as a foreground server the
|
||||
# studio would otherwise drain the rest of this piped script, leaving
|
||||
|
|
@ -3366,7 +3408,7 @@ if [ "$_SKIP_AUTOSTART" != true ] && [ -t 1 ]; then
|
|||
trap '' INT
|
||||
# `|| ...`: capture the exit code without set -e aborting first.
|
||||
_LAUNCH_EXIT=0
|
||||
(trap - INT; exec "$VENV_DIR/bin/unsloth" studio -p 8888 </dev/null) || _LAUNCH_EXIT=$?
|
||||
(trap - INT; exec "$VENV_DIR/bin/unsloth" studio -p "$_post_install_port" </dev/null) || _LAUNCH_EXIT=$?
|
||||
if [ "$_LAUNCH_EXIT" -ne 0 ] && [ "$_MIGRATED" = true ]; then
|
||||
echo ""
|
||||
echo "⚠️ Unsloth Studio failed to start after migration."
|
||||
|
|
|
|||
|
|
@ -94,7 +94,13 @@ assert_contains \
|
|||
# The post-install foreground launch honors the preference too.
|
||||
assert_contains \
|
||||
"install.sh: post-install launch opens browser via gated watcher" \
|
||||
"$_installer" "_post_install_browser_watch 8888"
|
||||
"$_installer" '_post_install_browser_watch "$_post_install_port"'
|
||||
assert_contains \
|
||||
"install.sh: post-install launch selects a free port" \
|
||||
"$_installer" "_find_post_install_port()"
|
||||
assert_contains \
|
||||
"install.sh: server uses the watcher-selected port" \
|
||||
"$_installer" 'studio -p "$_post_install_port"'
|
||||
# The --shortcuts-only early exit must not EPIPE a curl | sh pipeline.
|
||||
assert_contains \
|
||||
"install.sh: shortcuts-only exit drains piped stdin" \
|
||||
|
|
@ -182,6 +188,15 @@ assert_file_contains \
|
|||
assert_file_contains \
|
||||
"install.ps1: post-install launch opens browser via gated watcher" \
|
||||
"$INSTALL_PS1" 'Start-Job -ScriptBlock $_browserWatch'
|
||||
assert_file_contains \
|
||||
"install.ps1: post-install launch selects a free port" \
|
||||
"$INSTALL_PS1" 'function Find-PostInstallStudioPort {'
|
||||
assert_file_contains \
|
||||
"install.ps1: watcher receives the selected port" \
|
||||
"$INSTALL_PS1" 'ArgumentList @($_watchRootId, $_launchPort)'
|
||||
assert_file_contains \
|
||||
"install.ps1: server uses the watcher-selected port" \
|
||||
"$INSTALL_PS1" '& $UnslothExe studio -p $_launchPort'
|
||||
# All launcher URL opens must route through the gated helper. The one
|
||||
# allowed direct call is inside the post-install $_browserWatch scriptblock,
|
||||
# whose Start-Job call site is itself gated on the preference.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue