Compare commits

...

3 commits

Author SHA1 Message Date
3e2cb4165c Release 0.4.10: keep tray responsive during update/reconfigure.
Poll external processes instead of Start-Process -Wait on the UI thread, and restart the tray after a successful update.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-07 18:13:19 +08:00
e14f983aeb Release 0.4.9: update console exits cleanly; tray refreshes version.
Start NetX in-process after apply (no nested Wait hang) and reload version.json in the tray tip/menu.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-07 17:54:37 +08:00
66f9fa9782 Release 0.4.8: fix in-place update (elevate + ship python/runtime).
Apply updates as Administrator for Program Files, copy portable python with .venv, and wait for tray apply to finish.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-07 17:33:34 +08:00
7 changed files with 336 additions and 132 deletions

View file

@ -315,16 +315,24 @@ try {
}
$zipName = "NetX-$latest-win64.zip"
$zipPath = Join-Path $dlDir $zipName
Write-Host "==> Downloading $zipName from $($result.source) ..."
# Only attach token for non-GitHub hosts (Forgejo/private). Public GitHub assets need no auth;
# a Forgejo token would break anonymous GitHub downloads.
$dlHeaders = @{ "User-Agent" = "NetX-UpdateCheck" }
$dlUri = [uri]$result.download_url
$isGithub = ($dlUri.Host -match '(^|\.)github\.com$' -or $dlUri.Host -match '(^|\.)githubusercontent\.com$')
if ($UpdateToken -and -not $isGithub) {
$dlHeaders["Authorization"] = "token $UpdateToken"
$needDownload = $true
if ((Test-Path -LiteralPath $zipPath) -and ((Get-Item -LiteralPath $zipPath).Length -gt 1MB)) {
Write-Host "==> Using existing download: $zipPath ($([math]::Round((Get-Item $zipPath).Length/1MB,1)) MB)"
$needDownload = $false
}
if ($needDownload) {
Write-Host "==> Downloading $zipName from $($result.source) ..."
# Only attach token for non-GitHub hosts (Forgejo/private). Public GitHub assets need no auth;
# a Forgejo token would break anonymous GitHub downloads.
$dlHeaders = @{ "User-Agent" = "NetX-UpdateCheck" }
$dlUri = [uri]$result.download_url
$isGithub = ($dlUri.Host -match '(^|\.)github\.com$' -or $dlUri.Host -match '(^|\.)githubusercontent\.com$')
if ($UpdateToken -and -not $isGithub) {
$dlHeaders["Authorization"] = "token $UpdateToken"
}
Invoke-WebRequest -Uri $result.download_url -OutFile $zipPath -Headers $dlHeaders -UseBasicParsing
}
Invoke-WebRequest -Uri $result.download_url -OutFile $zipPath -Headers $dlHeaders -UseBasicParsing
if ($result.sha256 -and $result.sha256 -notmatch 'REPLACE' -and $result.sha256.Trim()) {
$hash = (Get-FileHash -Path $zipPath -Algorithm SHA256).Hash.ToLowerInvariant()
$expect = $result.sha256.ToLowerInvariant()
@ -334,7 +342,27 @@ try {
Write-Host "==> SHA256 OK"
}
# Program Files installs need admin to replace files; elevate once (avoid loop via NETX_UPDATE_ELEVATED).
$progWritable = $false
try {
$probe = Join-Path $prog (".netx_w_" + [guid]::NewGuid().ToString("n"))
[IO.File]::WriteAllText($probe, "x")
Remove-Item -LiteralPath $probe -Force -ErrorAction SilentlyContinue
$progWritable = $true
} catch {
$progWritable = $false
}
if (-not $progWritable -and -not $env:NETX_UPDATE_ELEVATED) {
Write-Host "==> Program directory is not writable; relaunching update as Administrator (UAC)..." -ForegroundColor Yellow
$arg = "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`" -ProgramRoot `"$prog`" -DataRoot `"$data`" -Apply"
$ep = Start-Process -FilePath "powershell.exe" -ArgumentList $arg -WorkingDirectory $prog `
-Verb RunAs -Wait -PassThru
if ($null -eq $ep.ExitCode) { exit 1 }
exit $ep.ExitCode
}
Write-Host "==> Applying update via update_netx.ps1"
$env:NETX_UPDATE_ELEVATED = "1"
& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "update_netx.ps1") `
-PackagePath $zipPath -ProgramRoot $prog -DataRoot $data
Write-Host "==> Update applied to $latest" -ForegroundColor Green

View file

@ -5,7 +5,7 @@
#define MyAppName "NetX"
#ifndef MyAppVersion
#define MyAppVersion "0.4.7"
#define MyAppVersion "0.4.10"
#endif
#define MyAppPublisher "NetX"
#define MyAppURL "https://github.com/hansjone/netx"

View file

@ -1,11 +1,11 @@
{
"channel": "stable",
"latest": "0.4.7",
"latest": "0.4.10",
"min_compatible": "0.3.0",
"notes_url": "https://github.com/hansjone/netx/releases/tag/v0.4.7",
"notes_url": "https://github.com/hansjone/netx/releases/tag/v0.4.10",
"windows": {
"url": "https://github.com/hansjone/netx/releases/download/v0.4.7/NetX-0.4.7-win64.zip",
"setup_url": "https://github.com/hansjone/netx/releases/download/v0.4.7/NetX-Setup-0.4.7.exe",
"url": "https://github.com/hansjone/netx/releases/download/v0.4.10/NetX-0.4.10-win64.zip",
"setup_url": "https://github.com/hansjone/netx/releases/download/v0.4.10/NetX-Setup-0.4.10.exe",
"sha256": "",
"size": 0
}

View file

@ -101,7 +101,8 @@ if ($autoVal -match '^(1|true|yes|on)$') {
$CheckUpdateOnLaunch = $true
}
$uiUrl = "http://${hostBind}:${port}/"
$ver = Get-NetxVersion -ProgramRoot $prog
$script:ver = Get-NetxVersion -ProgramRoot $prog
$ver = $script:ver
$dbMode = Get-DbMode -EnvMap $map
$dbModeLabel = if ($dbMode -eq "bundled") {
if (([cultureinfo]::CurrentUICulture.Name -match '^(zh|zh-)')) { "内置库" } else { "built-in DB" }
@ -160,16 +161,25 @@ function Invoke-NetxScript {
}
function Update-NetxTrayTip {
# Refresh version after in-place updates (tray process may outlive version.json).
try {
$nowVer = Get-NetxVersion -ProgramRoot $prog
if ($nowVer -and $nowVer -ne $script:ver) {
$script:ver = $nowVer
if ($null -ne $script:miHeader) { $script:miHeader.Text = "NetX $script:ver" }
}
} catch {}
$running = Test-NetxApiHealthy -HostName $hostBind -Port $port -TimeoutSec 1
$state = if ($running) {
(T "Running" "运行中")
} else {
(T "Stopped" "已停止")
}
$tip = "NetX $ver · $state · $dbModeLabel`n$uiUrl"
$v = $script:ver
$tip = "NetX $v · $state · $dbModeLabel`n$uiUrl"
if ($tip.Length -gt 63) {
# NotifyIcon.Text max ~63 chars on older Windows.
$tip = "NetX $ver · $state · $dbModeLabel"
$tip = "NetX $v · $state · $dbModeLabel"
}
$notify.Text = $tip
}
@ -198,6 +208,63 @@ function Start-NetxTrayDeferred {
$timer.Start()
}
function Start-NetxTrayWatchProcess {
# Never Start-Process -Wait on the WinForms UI thread — it freezes the tray menu.
param(
[Parameter(Mandatory = $true)]$Process,
[scriptblock]$OnExit
)
if ($null -eq $Process) {
if ($OnExit) { & $OnExit $null }
return
}
$script:netxWatchProc = $Process
$script:netxWatchOnExit = $OnExit
if ($script:netxWatchTimer) {
try { $script:netxWatchTimer.Stop(); $script:netxWatchTimer.Dispose() } catch {}
}
$timer = New-Object System.Windows.Forms.Timer
$timer.Interval = 400
$timer.add_Tick({
$p = $script:netxWatchProc
if ($null -eq $p) {
try { $script:netxWatchTimer.Stop(); $script:netxWatchTimer.Dispose() } catch {}
$script:netxWatchTimer = $null
return
}
$done = $false
try { $done = [bool]$p.HasExited } catch { $done = $true }
if (-not $done) { return }
try { $script:netxWatchTimer.Stop(); $script:netxWatchTimer.Dispose() } catch {}
$script:netxWatchTimer = $null
$cb = $script:netxWatchOnExit
$script:netxWatchOnExit = $null
$script:netxWatchProc = $null
if ($cb) { & $cb $p }
})
$script:netxWatchTimer = $timer
$timer.Start()
}
function Restart-NetxTraySelf {
# Fresh process picks up new version.json / scripts after in-place update.
try {
$notify.ShowBalloonTip(
2500, "NetX",
(T "Restarting tray…" "正在重启托盘…"),
[System.Windows.Forms.ToolTipIcon]::Info
)
} catch {}
$trayFile = Join-Path $PSScriptRoot "netx_tray.ps1"
$arg = "-NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File `"$trayFile`" -ProgramRoot `"$prog`" -DataRoot `"$data`""
try {
Start-Process -FilePath "powershell.exe" -ArgumentList $arg -WindowStyle Hidden | Out-Null
} catch {}
try { $notify.Visible = $false } catch {}
try { $form.Close() } catch {}
[System.Windows.Forms.Application]::Exit()
}
function Test-NetxSetupCancelled {
param([AllowNull()][Nullable[int]]$ExitCode)
if ($null -eq $ExitCode) { return $false }
@ -205,6 +272,43 @@ function Test-NetxSetupCancelled {
return ([int]$ExitCode -eq -1073741510)
}
function Complete-NetxTrayReconfig {
# Reload .env after reconfigure (host/port/mode may change).
if (Test-Path $envPath) {
$map = Read-DotEnv -Path $envPath
if ($map["NETX_HOST"]) { $script:hostBind = $map["NETX_HOST"]; $hostBind = $script:hostBind }
if ($map["NETX_PORT"]) {
try {
$script:port = [int]$map["NETX_PORT"]
$port = $script:port
} catch {}
}
$script:uiUrl = "http://${hostBind}:${port}/"
$uiUrl = $script:uiUrl
$dbMode = Get-DbMode -EnvMap $map
$script:dbModeLabel = if ($dbMode -eq "bundled") {
(T "built-in DB" "内置库")
} else {
(T "external DB" "外置库")
}
$dbModeLabel = $script:dbModeLabel
if ($miSub) { $miSub.Text = "$dbModeLabel · ${hostBind}:$port" }
}
$restart = [System.Windows.Forms.MessageBox]::Show(
(T "Database configuration saved.`nStart NetX now?" "数据库配置已保存。`n是否立即启动 NetX?"),
"NetX",
[System.Windows.Forms.MessageBoxButtons]::YesNo,
[System.Windows.Forms.MessageBoxIcon]::Information
)
if ($restart -eq [System.Windows.Forms.DialogResult]::Yes) {
Invoke-NetxScript -File $startPs1 -ExtraArgs @("-SkipBrowser") | Out-Null
Open-NetxUiWhenReady
} else {
Update-NetxTrayTip
}
}
function Open-NetxUiWhenReady {
param([int]$TimeoutSec = 180)
$notify.ShowBalloonTip(
@ -213,18 +317,37 @@ function Open-NetxUiWhenReady {
(T "Starting… first run may take a minute." "正在启动…首次迁移可能需要一分钟。"),
[System.Windows.Forms.ToolTipIcon]::Info
)
if (Wait-NetxApiHealthy -HostName $hostBind -Port $port -TimeoutSec $TimeoutSec) {
try { Start-Process $uiUrl } catch {}
Update-NetxTrayTip
return
# Poll on a timer — do not block the tray UI thread with Wait-NetxApiHealthy.
if ($script:netxUiReadyTimer) {
try { $script:netxUiReadyTimer.Stop(); $script:netxUiReadyTimer.Dispose() } catch {}
}
$notify.ShowBalloonTip(
8000,
"NetX",
(T "UI not ready yet. Use tray → Open UI when ready, or check data\runtime\netx.err.log." "界面尚未就绪。就绪后请用托盘「打开界面」,或查看 data\runtime\netx.err.log。"),
[System.Windows.Forms.ToolTipIcon]::Warning
)
Update-NetxTrayTip
$script:netxUiReadyTicks = 0
$script:netxUiReadyMax = [Math]::Max(1, [int]($TimeoutSec * 2))
$timer = New-Object System.Windows.Forms.Timer
$timer.Interval = 500
$timer.add_Tick({
$script:netxUiReadyTicks++
if (Test-NetxApiHealthy -HostName $hostBind -Port $port -TimeoutSec 2) {
try { $script:netxUiReadyTimer.Stop(); $script:netxUiReadyTimer.Dispose() } catch {}
$script:netxUiReadyTimer = $null
try { Start-Process $uiUrl } catch {}
Update-NetxTrayTip
return
}
if ($script:netxUiReadyTicks -ge $script:netxUiReadyMax) {
try { $script:netxUiReadyTimer.Stop(); $script:netxUiReadyTimer.Dispose() } catch {}
$script:netxUiReadyTimer = $null
$notify.ShowBalloonTip(
8000,
"NetX",
(T "UI not ready yet. Use tray → Open UI when ready, or check data\runtime\netx.err.log." "界面尚未就绪。就绪后请用托盘「打开界面」,或查看 data\runtime\netx.err.log。"),
[System.Windows.Forms.ToolTipIcon]::Warning
)
Update-NetxTrayTip
}
})
$script:netxUiReadyTimer = $timer
$timer.Start()
}
$form = New-Object System.Windows.Forms.Form
@ -297,7 +420,8 @@ $menu.Padding = New-Object System.Windows.Forms.Padding(4, 4, 4, 4)
$menu.Renderer = New-Object NetxMenuRenderer
# --- header (info only) ---
$miHeader = New-NetxMenuItem -Text "NetX $ver" -Header
$script:miHeader = New-NetxMenuItem -Text "NetX $script:ver" -Header
$miHeader = $script:miHeader
[void]$menu.Items.Add($miHeader)
$miSub = New-NetxMenuItem -Text "$dbModeLabel · ${hostBind}:$port" -Muted
@ -359,7 +483,7 @@ $miReconfig.add_Click({
if ($ans -ne [System.Windows.Forms.DialogResult]::Yes) { return }
$notify.ShowBalloonTip(3000, "NetX", (T "Stopping…" "正在停止…"), [System.Windows.Forms.ToolTipIcon]::Info)
Invoke-NetxScript -File $stopPs1 -Wait | Out-Null
Invoke-NetxScript -File $stopPs1 | Out-Null
# ProgramData\.env is often admin-owned after Setup; repair ACL before reconfigure.
$null = Ensure-NetxDataAcl -DataRoot $data -Quiet
@ -367,48 +491,61 @@ $miReconfig.add_Click({
try {
$p = Start-NetxPowerShell -File $setupPs1 `
-Arguments @("-ProgramRoot", $prog, "-DataRoot", $data) `
-WorkingDirectory $prog -WindowStyle Normal -Wait -PassThru
if (Test-NetxSetupCancelled -ExitCode $p.ExitCode) {
$notify.ShowBalloonTip(
4000, "NetX",
(T "Database setup cancelled." "已取消数据库配置。"),
[System.Windows.Forms.ToolTipIcon]::Info
)
Update-NetxTrayTip
return
}
if ($null -ne $p.ExitCode -and $p.ExitCode -ne 0) {
# Access denied → offer elevated re-run (UAC).
$elev = [System.Windows.Forms.MessageBox]::Show(
(T `
"Database setup failed (exit $($p.ExitCode)).`n`nIf this was a permission error on %ProgramData%\NetX\.env, click Yes to retry as Administrator." `
"数据库配置失败(退出码 $($p.ExitCode))。`n`n若是 %ProgramData%\NetX\.env 权限问题,点「是」将以管理员身份重试。"),
"NetX",
[System.Windows.Forms.MessageBoxButtons]::YesNo,
[System.Windows.Forms.MessageBoxIcon]::Warning
)
if ($elev -eq [System.Windows.Forms.DialogResult]::Yes) {
$arg = "-NoProfile -ExecutionPolicy Bypass -File `"$setupPs1`" -ProgramRoot `"$prog`" -DataRoot `"$data`""
$ep = Start-Process -FilePath "powershell.exe" -ArgumentList $arg `
-WorkingDirectory $prog -Verb RunAs -Wait -PassThru
if (Test-NetxSetupCancelled -ExitCode $ep.ExitCode) {
Update-NetxTrayTip
return
}
if ($null -ne $ep.ExitCode -and $ep.ExitCode -ne 0) {
[System.Windows.Forms.MessageBox]::Show(
(T "Elevated database setup still failed (exit $($ep.ExitCode))." "管理员配置仍失败(退出码 $($ep.ExitCode))。"),
"NetX",
[System.Windows.Forms.MessageBoxButtons]::OK,
[System.Windows.Forms.MessageBoxIcon]::Error
)
Update-NetxTrayTip
return
}
} else {
-WorkingDirectory $prog -WindowStyle Normal -PassThru
Start-NetxTrayWatchProcess -Process $p -OnExit {
param($sp)
if ($null -eq $sp) { Update-NetxTrayTip; return }
if (Test-NetxSetupCancelled -ExitCode $sp.ExitCode) {
$notify.ShowBalloonTip(
4000, "NetX",
(T "Database setup cancelled." "已取消数据库配置。"),
[System.Windows.Forms.ToolTipIcon]::Info
)
Update-NetxTrayTip
return
}
if ($null -ne $sp.ExitCode -and $sp.ExitCode -ne 0) {
$elev = [System.Windows.Forms.MessageBox]::Show(
(T `
"Database setup failed (exit $($sp.ExitCode)).`n`nIf this was a permission error on %ProgramData%\NetX\.env, click Yes to retry as Administrator." `
"数据库配置失败(退出码 $($sp.ExitCode))。`n`n若是 %ProgramData%\NetX\.env 权限问题,点「是」将以管理员身份重试。"),
"NetX",
[System.Windows.Forms.MessageBoxButtons]::YesNo,
[System.Windows.Forms.MessageBoxIcon]::Warning
)
if ($elev -ne [System.Windows.Forms.DialogResult]::Yes) {
Update-NetxTrayTip
return
}
$arg = "-NoProfile -ExecutionPolicy Bypass -File `"$setupPs1`" -ProgramRoot `"$prog`" -DataRoot `"$data`""
try {
$ep = Start-Process -FilePath "powershell.exe" -ArgumentList $arg `
-WorkingDirectory $prog -Verb RunAs -PassThru
} catch {
Update-NetxTrayTip
return
}
Start-NetxTrayWatchProcess -Process $ep -OnExit {
param($ep2)
if ($null -eq $ep2 -or (Test-NetxSetupCancelled -ExitCode $ep2.ExitCode)) {
Update-NetxTrayTip
return
}
if ($null -ne $ep2.ExitCode -and $ep2.ExitCode -ne 0) {
[System.Windows.Forms.MessageBox]::Show(
(T "Elevated database setup still failed (exit $($ep2.ExitCode))." "管理员配置仍失败(退出码 $($ep2.ExitCode))。"),
"NetX",
[System.Windows.Forms.MessageBoxButtons]::OK,
[System.Windows.Forms.MessageBoxIcon]::Error
)
Update-NetxTrayTip
return
}
Complete-NetxTrayReconfig
}
return
}
Complete-NetxTrayReconfig
}
} catch {
[System.Windows.Forms.MessageBox]::Show(
@ -418,35 +555,6 @@ $miReconfig.add_Click({
[System.Windows.Forms.MessageBoxIcon]::Error
)
Update-NetxTrayTip
return
}
# Reload .env after reconfigure (host/port/mode may change).
if (Test-Path $envPath) {
$map = Read-DotEnv -Path $envPath
if ($map["NETX_HOST"]) { $hostBind = $map["NETX_HOST"] }
if ($map["NETX_PORT"]) { try { $port = [int]$map["NETX_PORT"] } catch {} }
$uiUrl = "http://${hostBind}:${port}/"
$dbMode = Get-DbMode -EnvMap $map
$dbModeLabel = if ($dbMode -eq "bundled") {
(T "built-in DB" "内置库")
} else {
(T "external DB" "外置库")
}
$miSub.Text = "$dbModeLabel · ${hostBind}:$port"
}
$restart = [System.Windows.Forms.MessageBox]::Show(
(T "Database configuration saved.`nStart NetX now?" "数据库配置已保存。`n是否立即启动 NetX?"),
"NetX",
[System.Windows.Forms.MessageBoxButtons]::YesNo,
[System.Windows.Forms.MessageBoxIcon]::Information
)
if ($restart -eq [System.Windows.Forms.DialogResult]::Yes) {
Invoke-NetxScript -File $startPs1 -ExtraArgs @("-SkipBrowser") | Out-Null
Open-NetxUiWhenReady
} else {
Update-NetxTrayTip
}
}
})
@ -456,25 +564,78 @@ $miUpdate = New-NetxMenuItem -Text (T "Check for updates…" "检查更新…")
$miUpdate.add_Click({
Start-NetxTrayDeferred {
try {
$notify.ShowBalloonTip(
3000, "NetX",
(T "Checking for updates…" "正在检查更新…"),
[System.Windows.Forms.ToolTipIcon]::Info
)
# No -Wait on UI thread (freezes ContextMenuStrip / tray).
$p = Start-NetxPowerShell -File $checkPs1 -Arguments @("-ProgramRoot", $prog, "-DataRoot", $data) `
-WorkingDirectory $prog -WindowStyle Normal -Wait -PassThru
if (Test-NetxSetupCancelled -ExitCode $p.ExitCode) { return }
if ($p.ExitCode -eq 10) {
$ans = [System.Windows.Forms.MessageBox]::Show(
(T "A newer NetX is available. Download and apply now?" "发现新版本,是否立即下载并更新?"),
(T "NetX update" "NetX 更新"),
[System.Windows.Forms.MessageBoxButtons]::YesNo,
[System.Windows.Forms.MessageBoxIcon]::Question
)
if ($ans -eq [System.Windows.Forms.DialogResult]::Yes) {
Start-NetxPowerShell -File $checkPs1 -Arguments @("-ProgramRoot", $prog, "-DataRoot", $data, "-Apply") `
-WorkingDirectory $prog -WindowStyle Normal | Out-Null
-WorkingDirectory $prog -WindowStyle Normal -PassThru
Start-NetxTrayWatchProcess -Process $p -OnExit {
param($cp)
try {
if ($null -eq $cp) { return }
if (Test-NetxSetupCancelled -ExitCode $cp.ExitCode) { return }
if ($cp.ExitCode -eq 10) {
$ans = [System.Windows.Forms.MessageBox]::Show(
(T "A newer NetX is available. Download and apply now?" "发现新版本,是否立即下载并更新?"),
(T "NetX update" "NetX 更新"),
[System.Windows.Forms.MessageBoxButtons]::YesNo,
[System.Windows.Forms.MessageBoxIcon]::Question
)
if ($ans -ne [System.Windows.Forms.DialogResult]::Yes) { return }
$arg = "-NoProfile -ExecutionPolicy Bypass -File `"$checkPs1`" -ProgramRoot `"$prog`" -DataRoot `"$data`" -Apply"
$notify.ShowBalloonTip(
5000, "NetX",
(T "Downloading / applying update… keep the console open." "正在下载/应用更新…请勿关闭控制台窗口。"),
[System.Windows.Forms.ToolTipIcon]::Info
)
try {
$ap = Start-Process -FilePath "powershell.exe" -ArgumentList $arg `
-WorkingDirectory $prog -WindowStyle Normal -Verb RunAs -PassThru
} catch {
$notify.ShowBalloonTip(4000, "NetX", (T "Update cancelled (UAC)." "已取消更新(UAC)。"), [System.Windows.Forms.ToolTipIcon]::Info)
return
}
Start-NetxTrayWatchProcess -Process $ap -OnExit {
param($ap2)
if ($null -eq $ap2) { return }
if (Test-NetxSetupCancelled -ExitCode $ap2.ExitCode) {
$notify.ShowBalloonTip(4000, "NetX", (T "Update cancelled." "已取消更新。"), [System.Windows.Forms.ToolTipIcon]::Info)
return
}
if ($null -ne $ap2.ExitCode -and $ap2.ExitCode -eq 0) {
$newVer = Get-NetxVersion -ProgramRoot $prog
[System.Windows.Forms.MessageBox]::Show(
(T "Updated to $newVer.`nTray will restart." "已更新到 $newVer。`n即将重启托盘。"),
(T "NetX update" "NetX 更新"),
[System.Windows.Forms.MessageBoxButtons]::OK,
[System.Windows.Forms.MessageBoxIcon]::Information
)
Restart-NetxTraySelf
return
}
[System.Windows.Forms.MessageBox]::Show(
(T "Update failed (exit $($ap2.ExitCode)). Re-run Check for updates, or install NetX-Setup manually." "更新失败(退出码 $($ap2.ExitCode))。请重试「检查更新」,或手动运行 Setup 安装包。"),
(T "NetX update" "NetX 更新"),
[System.Windows.Forms.MessageBoxButtons]::OK,
[System.Windows.Forms.MessageBoxIcon]::Warning
)
Update-NetxTrayTip
}
} elseif ($cp.ExitCode -eq 0) {
[System.Windows.Forms.MessageBox]::Show(
(T "NetX is up to date ($script:ver)." "已是最新版本 ($script:ver)。"),
(T "NetX update" "NetX 更新")
)
}
} catch {
[System.Windows.Forms.MessageBox]::Show(
(T "Update check failed: $($_.Exception.Message)" "检查更新失败:$($_.Exception.Message)"),
"NetX"
)
}
} elseif ($p.ExitCode -eq 0) {
[System.Windows.Forms.MessageBox]::Show(
(T "NetX is up to date ($ver)." "已是最新版本 ($ver)。"),
(T "NetX update" "NetX 更新")
)
}
} catch {
[System.Windows.Forms.MessageBox]::Show(

View file

@ -1,4 +1,4 @@
param(
param(
[string]$ProgramRoot = "",
[string]$DataRoot = "",
[switch]$SkipBrowser = $false,
@ -118,21 +118,25 @@ if (-not (Test-Path $startScript)) {
throw "start_netx.ps1 not found at $startScript"
}
$startExtra = @(
"-SkipInstall", "-Background",
"-BindHost", $hostBind, "-Port", "$port"
)
if ($InlineSchedulers) { $startExtra += "-InlineSchedulers" }
Write-Host "==> Starting NetX (API + workers; UI via API on :$port)"
$startProc = Start-NetxPowerShell -File $startScript -Arguments $startExtra `
-WorkingDirectory $prog -WindowStyle Hidden -Wait -PassThru
if ($startProc.ExitCode -ne 0) { exit $startProc.ExitCode }
# Run in-process with -Background so this console exits after /health (nested
# Start-Process -Wait on a Hidden child often never returns under UAC update).
$startArgs = @{
SkipInstall = $true
Background = $true
BindHost = $hostBind
Port = $port
}
if ($InlineSchedulers) { $startArgs["InlineSchedulers"] = $true }
& $startScript @startArgs
if ($LASTEXITCODE -and $LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
$url = "http://${hostBind}:${port}/"
if (-not (Wait-NetxApiHealthy -HostName $hostBind -Port $port -TimeoutSec 30)) {
Write-Host "[ERR] NetX process exited 0 but /health not ready: $url" -ForegroundColor Red
exit 1
if (-not (Test-NetxApiHealthy -HostName $hostBind -Port $port -TimeoutSec 2)) {
if (-not (Wait-NetxApiHealthy -HostName $hostBind -Port $port -TimeoutSec 30)) {
Write-Host "[ERR] NetX started but /health not ready: $url" -ForegroundColor Red
exit 1
}
}
Write-Host "==> Open: $url" -ForegroundColor Green
if (-not $SkipBrowser) {

View file

@ -90,7 +90,8 @@ try {
-ProgramRoot $prog -DataRoot $data
# Replace program layers only — never wipe data root.
$replaceDirs = @("netx_api", "alembic", "web", "packaging", "scripts", "postgres", "packages")
# Include python/ (portable runtime) — required with shipped .venv since 0.4.6.
$replaceDirs = @("netx_api", "alembic", "web", "packaging", "scripts", "postgres", "packages", "python")
foreach ($name in $replaceDirs) {
$from = Join-Path $src $name
$to = Join-Path $prog $name
@ -107,7 +108,7 @@ try {
Copy-Item -Path $from -Destination (Join-Path $prog $f) -Force
}
}
# Optional runtime / .venv shipped in package
# Legacy top-level runtime/ (older packages) + shipped .venv
if (Test-Path (Join-Path $src "runtime")) {
$rt = Join-Path $prog "runtime"
if (Test-Path $rt) { Remove-Item -Recurse -Force $rt }
@ -120,11 +121,21 @@ try {
Copy-Item -Path (Join-Path $src ".venv") -Destination $venvTo -Recurse -Force
}
# Builder-path pyvenv.cfg → local python/runtime (same as first install).
if (Get-Command Repair-NetxShippedVenv -ErrorAction SilentlyContinue) {
if (Repair-NetxShippedVenv -ProgramRoot $prog) {
Write-Host "==> Relinked .venv to bundled python/runtime" -ForegroundColor Green
}
}
Write-Host "==> Update files applied" -ForegroundColor Green
if (-not $NoStart) {
& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "start_netx_app.ps1") `
# Same process (no nested powershell) so the update console can exit cleanly.
& (Join-Path $PSScriptRoot "start_netx_app.ps1") `
-ProgramRoot $prog -DataRoot $data -SkipBrowser
if ($LASTEXITCODE -and $LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
}
Write-Host "==> Update complete. You can close this window." -ForegroundColor Green
} finally {
if (Test-Path $work) {
Remove-Item -Recurse -Force $work -ErrorAction SilentlyContinue

View file

@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "netx-ops"
version = "0.4.7"
version = "0.4.10"
description = "netx operations tool: alarm-centric workflows with REST API and stdio MCP"
readme = "README.md"
requires-python = ">=3.11"