mirror of
https://github.com/hansjone/netx.git
synced 2026-10-08 22:20:58 +08:00
Fix portable Python: ship python/runtime and relink .venv on install (0.4.6).
Some checks failed
Release Windows / build (push) Has been cancelled
Some checks failed
Release Windows / build (push) Has been cancelled
0.4.5 venv pointed at the build machine user profile; other PCs failed to start API. Bundle stdlib under python/runtime, repair pyvenv.cfg, and health-check before start. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
3a89438e5b
commit
5429c07342
7 changed files with 158 additions and 27 deletions
|
|
@ -19,7 +19,7 @@ This directory builds a Windows deliverable with:
|
|||
|
||||
**Installer:** English + 简体中文 (language dialog). Icons use `packaging/assets/netx.ico`.
|
||||
|
||||
**Offline:** Setup ships portable PostgreSQL, `.venv` (when built with `-CreateVenv`), and WinSW. The installer wizard chooses **built-in or external** PostgreSQL and validates external credentials (`psql SELECT 1`) before files are installed — **no GitHub/EDB download on the target PC**. Service install uses bundled WinSW (pass `-AllowDownload` only on a build/dev machine if the binary is missing). Auto-update still needs network later, and is unchecked by default.
|
||||
**Offline:** Setup ships portable PostgreSQL, **`python/runtime` + `.venv`** (portable; not tied to the build PC’s user profile), and WinSW. The installer wizard chooses **built-in or external** PostgreSQL and validates external credentials (`psql SELECT 1`) before files are installed — **no GitHub/EDB download on the target PC**. Service install uses bundled WinSW (pass `-AllowDownload` only on a build/dev machine if the binary is missing). Auto-update still needs network later, and is unchecked by default.
|
||||
|
||||
**OS:** Windows 10/11 or Windows Server **2016+** recommended. Packaging scripts are UTF-8 **with BOM** so Chinese UI works on Windows PowerShell 5.x. Bundled Python in current releases is **3.13+**, which does **not** support Windows Server 2012 R2 — use Server 2016+ or a newer desktop OS.
|
||||
|
||||
|
|
|
|||
|
|
@ -276,12 +276,21 @@ function Start-NetxPowerShell {
|
|||
function Get-NetxSystemPython {
|
||||
# Prefer a real interpreter; skip the WindowsApps Store stub.
|
||||
$candidates = @()
|
||||
foreach ($cmd in @("python", "python3", "py")) {
|
||||
foreach ($cmd in @("python", "python3")) {
|
||||
$c = Get-Command $cmd -ErrorAction SilentlyContinue
|
||||
if ($c -and $c.Source -and ($c.Source -notmatch '\\WindowsApps\\')) {
|
||||
$candidates += $c.Source
|
||||
}
|
||||
}
|
||||
$pyLauncher = Get-Command "py" -ErrorAction SilentlyContinue
|
||||
if ($pyLauncher -and $pyLauncher.Source -and ($pyLauncher.Source -notmatch '\\WindowsApps\\')) {
|
||||
try {
|
||||
$resolved = (& $pyLauncher.Source -3 -c "import sys; print(sys.executable)" 2>$null | Out-String).Trim()
|
||||
if ($resolved -and (Test-Path -LiteralPath $resolved)) {
|
||||
$candidates += $resolved
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
foreach ($p in @(
|
||||
"$env:LOCALAPPDATA\Python\pythoncore-3.14-64\python.exe",
|
||||
"$env:LOCALAPPDATA\Programs\Python\Python312\python.exe",
|
||||
|
|
@ -300,6 +309,65 @@ function Get-NetxSystemPython {
|
|||
return $null
|
||||
}
|
||||
|
||||
function Get-NetxBundledPythonRoot {
|
||||
param([Parameter(Mandatory = $true)][string]$ProgramRoot)
|
||||
return Join-Path $ProgramRoot "python\runtime"
|
||||
}
|
||||
|
||||
function Repair-NetxShippedVenv {
|
||||
param([Parameter(Mandatory = $true)][string]$ProgramRoot)
|
||||
$cfgPath = Join-Path $ProgramRoot ".venv\pyvenv.cfg"
|
||||
$rtRoot = Get-NetxBundledPythonRoot -ProgramRoot $ProgramRoot
|
||||
$rtPy = Join-Path $rtRoot "python.exe"
|
||||
if (-not (Test-Path -LiteralPath $rtPy)) { return $false }
|
||||
if (-not (Test-Path -LiteralPath $cfgPath)) { return $false }
|
||||
|
||||
$rtRootAbs = (Resolve-Path -LiteralPath $rtRoot).Path
|
||||
$rtPyAbs = (Resolve-Path -LiteralPath $rtPy).Path
|
||||
$ver = "3.14.0"
|
||||
try {
|
||||
$verOut = & $rtPyAbs -c "import sys; print('%d.%d.%d' % sys.version_info[:3])" 2>$null
|
||||
if ($verOut) { $ver = ($verOut | Out-String).Trim() }
|
||||
} catch {}
|
||||
|
||||
$lines = @(
|
||||
"home = $rtRootAbs",
|
||||
"include-system-site-packages = false",
|
||||
"version = $ver",
|
||||
"executable = $rtPyAbs"
|
||||
)
|
||||
Set-Content -LiteralPath $cfgPath -Value ($lines -join "`r`n") -Encoding ascii
|
||||
return $true
|
||||
}
|
||||
|
||||
function Test-NetxVenvRunnable {
|
||||
param([Parameter(Mandatory = $true)][string]$VenvPython)
|
||||
if (-not (Test-Path -LiteralPath $VenvPython)) { return $false }
|
||||
|
||||
$outFile = Join-Path $env:TEMP ("netx-venv-out-" + [Guid]::NewGuid().ToString("n") + ".txt")
|
||||
$errFile = Join-Path $env:TEMP ("netx-venv-err-" + [Guid]::NewGuid().ToString("n") + ".txt")
|
||||
try {
|
||||
$psi = New-Object System.Diagnostics.ProcessStartInfo
|
||||
$psi.FileName = $VenvPython
|
||||
$psi.Arguments = '-c "import encodings, sys; sys.exit(0 if sys.prefix else 1)"'
|
||||
$psi.UseShellExecute = $false
|
||||
$psi.RedirectStandardOutput = $true
|
||||
$psi.RedirectStandardError = $true
|
||||
$psi.CreateNoWindow = $true
|
||||
$p = [Diagnostics.Process]::Start($psi)
|
||||
$stderr = $p.StandardError.ReadToEnd()
|
||||
$null = $p.StandardOutput.ReadToEnd()
|
||||
$p.WaitForExit()
|
||||
if ($stderr -match 'did not find executable|No Python at|Fatal Python error') {
|
||||
return $false
|
||||
}
|
||||
if ($p.ExitCode -ne 0) { return $false }
|
||||
return $true
|
||||
} finally {
|
||||
Remove-Item -LiteralPath $outFile, $errFile -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
|
||||
function Ensure-NetxVenv {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$ProgramRoot,
|
||||
|
|
@ -307,9 +375,28 @@ function Ensure-NetxVenv {
|
|||
)
|
||||
$venvPy = Join-Path $ProgramRoot ".venv\Scripts\python.exe"
|
||||
$req = Join-Path $ProgramRoot "requirements.txt"
|
||||
if ((Test-Path $venvPy) -and -not $ForcePip) {
|
||||
$rtPy = Join-Path (Get-NetxBundledPythonRoot -ProgramRoot $ProgramRoot) "python.exe"
|
||||
|
||||
if (Test-Path -LiteralPath $rtPy) {
|
||||
$null = Repair-NetxShippedVenv -ProgramRoot $ProgramRoot
|
||||
}
|
||||
|
||||
if ((Test-Path -LiteralPath $venvPy) -and -not $ForcePip) {
|
||||
if (Test-NetxVenvRunnable -VenvPython $venvPy) {
|
||||
return $venvPy
|
||||
}
|
||||
Write-Host "[WARN] Shipped .venv exists but Python cannot start (broken pyvenv.cfg or missing runtime)." -ForegroundColor Yellow
|
||||
if (Test-Path -LiteralPath $rtPy) {
|
||||
$null = Repair-NetxShippedVenv -ProgramRoot $ProgramRoot
|
||||
if (Test-NetxVenvRunnable -VenvPython $venvPy) {
|
||||
Write-Host "==> Repaired .venv to use bundled python/runtime" -ForegroundColor Green
|
||||
return $venvPy
|
||||
}
|
||||
}
|
||||
if (-not $ForcePip) {
|
||||
throw "venv_not_runnable: reinstall NetX Setup (ships python/runtime + .venv) or run repair as admin"
|
||||
}
|
||||
}
|
||||
if (-not (Test-Path $venvPy)) {
|
||||
$py = Get-NetxSystemPython
|
||||
if (-not $py) {
|
||||
|
|
|
|||
|
|
@ -168,16 +168,50 @@ foreach ($cmdName in @("NetX-FirstRun.cmd", "NetX-Start.cmd", "NetX-Tray.cmd", "
|
|||
}
|
||||
|
||||
if ($CreateVenv) {
|
||||
Write-Host "==> Creating .venv in stage (ships with Setup; target should NOT pip-install)"
|
||||
Write-Host "==> Creating portable python/runtime + .venv (must not reference build-machine paths)"
|
||||
$pyPath = Get-NetxSystemPython
|
||||
if (-not $pyPath) { throw "python_not_found_for_venv: need Python 3.11+ (not WindowsApps stub)" }
|
||||
Write-Host " Using: $pyPath"
|
||||
& $pyPath -m venv (Join-Path $stage ".venv")
|
||||
$venvPy = Join-Path $stage ".venv\Scripts\python.exe"
|
||||
Write-Host " Build Python: $pyPath"
|
||||
|
||||
$prefix = (& $pyPath -c "import sys; print(sys.base_prefix)" 2>$null | Out-String).Trim()
|
||||
if (-not $prefix -or -not (Test-Path -LiteralPath $prefix)) {
|
||||
throw "python_base_prefix_not_found: $prefix"
|
||||
}
|
||||
$rtDir = Join-Path $stage "python\runtime"
|
||||
if (Test-Path $rtDir) { Remove-Item -Recurse -Force $rtDir }
|
||||
New-Item -ItemType Directory -Path $rtDir -Force | Out-Null
|
||||
|
||||
Write-Host "==> Copying Python stdlib/runtime to $rtDir (exclude base site-packages)"
|
||||
$spEx = Join-Path $prefix "Lib\site-packages"
|
||||
$robocopy = Join-Path $env:SystemRoot "System32\robocopy.exe"
|
||||
if (-not (Test-Path -LiteralPath $robocopy)) { throw "robocopy_not_found" }
|
||||
& $robocopy $prefix $rtDir /E /XD $spEx __pycache__ /XF *.pyc /NFL /NDL /NJH /NJS /nc /ns /np | Out-Null
|
||||
if ($LASTEXITCODE -ge 8) { throw "robocopy_python_runtime_failed: exit $LASTEXITCODE" }
|
||||
|
||||
$rtPy = Join-Path $rtDir "python.exe"
|
||||
if (-not (Test-Path -LiteralPath $rtPy)) { throw "python_runtime_missing: $rtPy" }
|
||||
|
||||
$venvDir = Join-Path $stage ".venv"
|
||||
if (Test-Path $venvDir) { Remove-Item -Recurse -Force $venvDir }
|
||||
Write-Host "==> Creating .venv from shipped runtime (--copies)"
|
||||
& $rtPy -m venv $venvDir --copies
|
||||
if ($LASTEXITCODE -ne 0) { throw "venv_create_failed" }
|
||||
|
||||
$venvPy = Join-Path $venvDir "Scripts\python.exe"
|
||||
$null = Repair-NetxShippedVenv -ProgramRoot $stage
|
||||
if (-not (Test-NetxVenvRunnable -VenvPython $venvPy)) {
|
||||
throw "venv_not_runnable_after_build: check python/runtime copy"
|
||||
}
|
||||
|
||||
& $venvPy -m pip install --upgrade pip
|
||||
& $venvPy -m pip install -r (Join-Path $stage "requirements.txt")
|
||||
if ($LASTEXITCODE -ne 0) { throw "pip_install_failed" }
|
||||
|
||||
$null = Repair-NetxShippedVenv -ProgramRoot $stage
|
||||
if (-not (Test-NetxVenvRunnable -VenvPython $venvPy)) {
|
||||
throw "venv_not_runnable_after_pip"
|
||||
}
|
||||
|
||||
Write-Host "==> Slimming .venv (drop tests / __pycache__ / *.pyc)"
|
||||
$site = Join-Path $stage ".venv\Lib\site-packages"
|
||||
if (Test-Path $site) {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
#define MyAppName "NetX"
|
||||
#ifndef MyAppVersion
|
||||
#define MyAppVersion "0.4.5"
|
||||
#define MyAppVersion "0.4.6"
|
||||
#endif
|
||||
#define MyAppPublisher "NetX"
|
||||
#define MyAppURL "https://github.com/hansjone/netx"
|
||||
|
|
|
|||
|
|
@ -293,21 +293,19 @@ Write-DotEnvValue -Path $envPath -Values $values
|
|||
Write-Host ""
|
||||
Write-Host "Wrote $envPath" -ForegroundColor Green
|
||||
|
||||
# Official Setup ships .venv (build_release -CreateVenv). Only bootstrap if missing
|
||||
# (e.g. zip without venv). Prefer not to pip-install on the target machine.
|
||||
# Official Setup ships python/runtime + .venv (build_release -CreateVenv).
|
||||
$venvPy = Join-Path $prog ".venv\Scripts\python.exe"
|
||||
if (Test-Path $venvPy) {
|
||||
Write-Host "==> Using bundled Python venv: $venvPy" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "==> Bundled .venv missing — creating one (Setup should normally ship it)." -ForegroundColor Yellow
|
||||
try {
|
||||
$null = Ensure-NetxVenv -ProgramRoot $prog
|
||||
Write-Host "==> Python venv ready: $venvPy" -ForegroundColor Green
|
||||
Write-Host "==> Bundled Python venv OK: $venvPy" -ForegroundColor Green
|
||||
} catch {
|
||||
if (Test-Path $venvPy) {
|
||||
throw
|
||||
}
|
||||
Write-Host "==> Bundled .venv missing — creating one (Setup should normally ship it)." -ForegroundColor Yellow
|
||||
Write-Host "[WARN] $($_.Exception.Message)" -ForegroundColor Yellow
|
||||
Write-Host " Install a Setup built with: packaging\build_release.ps1 -CreateVenv" -ForegroundColor Yellow
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host (T "Setup complete." "首次配置完成。") -ForegroundColor Green
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||
|
||||
[project]
|
||||
name = "netx-ops"
|
||||
version = "0.4.5"
|
||||
version = "0.4.6"
|
||||
description = "netx operations tool: alarm-centric workflows with REST API and stdio MCP"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.11"
|
||||
|
|
|
|||
|
|
@ -23,6 +23,14 @@ if ($Backgtound -and -not $Background) {
|
|||
$projectRoot = Split-Path -Parent $PSScriptRoot
|
||||
Set-Location $projectRoot
|
||||
|
||||
$packCommon = Join-Path $projectRoot "packaging\_common.ps1"
|
||||
if (Test-Path -LiteralPath $packCommon) {
|
||||
. $packCommon
|
||||
if (Get-Command Repair-NetxShippedVenv -ErrorAction SilentlyContinue) {
|
||||
$null = Repair-NetxShippedVenv -ProgramRoot $projectRoot
|
||||
}
|
||||
}
|
||||
|
||||
# Prefer writable data-root runtime dir (Program Files is not writable after Setup install).
|
||||
if ($env:NETX_RUN_DIR) {
|
||||
$runDir = $env:NETX_RUN_DIR
|
||||
|
|
@ -46,14 +54,18 @@ $webPidFile = Join-Path $runDir "web.pid"
|
|||
$webLogFile = Join-Path $runDir "web.out.log"
|
||||
$webErrFile = Join-Path $runDir "web.err.log"
|
||||
|
||||
$venvPython = Join-Path $projectRoot ".venv\\Scripts\\python.exe"
|
||||
if (-not (Test-Path $venvPython)) {
|
||||
$venvPython = Join-Path $projectRoot ".venv\Scripts\python.exe"
|
||||
if (Get-Command Test-NetxVenvRunnable -ErrorAction SilentlyContinue) {
|
||||
if (-not (Test-NetxVenvRunnable -VenvPython $venvPython)) {
|
||||
throw "venv_not_runnable: reinstall NetX or run setup as administrator"
|
||||
}
|
||||
} elseif (-not (Test-Path -LiteralPath $venvPython)) {
|
||||
Write-Host "==> .venv not found, creating virtual environment"
|
||||
python -m venv (Join-Path $projectRoot ".venv")
|
||||
}
|
||||
if (-not (Test-Path $venvPython)) {
|
||||
if (-not (Test-Path -LiteralPath $venvPython)) {
|
||||
throw "failed_to_create_venv"
|
||||
}
|
||||
}
|
||||
$pythonExe = $venvPython
|
||||
|
||||
Write-Host "==> Project root: $projectRoot"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue