mirror of
https://github.com/hansjone/netx.git
synced 2026-10-10 12:40:44 +08:00
Release 0.4.5: installer DB wizard, credential key, lean offline Setup.
Choose bundled or external PostgreSQL in the Setup wizard with connection validation; optional NETX_CREDENTIAL_SECRET_KEY; single desktop shortcut; faster uninstall and tray startup for offline Windows packages. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
86dfd41f44
commit
97393cd407
20 changed files with 1531 additions and 146 deletions
|
|
@ -1,4 +1,4 @@
|
|||
# Shared path helpers for Windows packaging scripts.
|
||||
# Shared path helpers for Windows packaging scripts.
|
||||
# Dot-source: . "$PSScriptRoot\_common.ps1"
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
|
@ -147,6 +147,7 @@ function Get-NetxDataEnvMap {
|
|||
return @{
|
||||
"NETX_AUTH_MCP_TOKEN_FILE" = (ConvertTo-NetxFsPath (Join-Path $d "data\auth\mcp_token"))
|
||||
"NETX_AUTH_SECRET_FILE" = (ConvertTo-NetxFsPath (Join-Path $d "data\auth\jwt_secret"))
|
||||
"NETX_CREDENTIAL_SECRET_FILE" = (ConvertTo-NetxFsPath (Join-Path $d "data\auth\credential_secret"))
|
||||
"NETX_SCHEDULER_HEARTBEAT_PATH" = (ConvertTo-NetxFsPath (Join-Path $d "data\runtime\scheduler_heartbeat.json"))
|
||||
"NETX_RUN_DIR" = (ConvertTo-NetxFsPath (Join-Path $d "data\runtime"))
|
||||
"NETX_BIZ_STATE_SPOOL_DIR" = (ConvertTo-NetxFsPath (Join-Path $d "data\biz_state_spool"))
|
||||
|
|
@ -156,6 +157,18 @@ function Get-NetxDataEnvMap {
|
|||
}
|
||||
}
|
||||
|
||||
function New-NetxFernetKey {
|
||||
# cryptography.fernet.Fernet.generate_key() == urlsafe_b64encode(os.urandom(32))
|
||||
$bytes = New-Object byte[] 32
|
||||
$rng = [System.Security.Cryptography.RandomNumberGenerator]::Create()
|
||||
try {
|
||||
$rng.GetBytes($bytes)
|
||||
} finally {
|
||||
$rng.Dispose()
|
||||
}
|
||||
return [Convert]::ToBase64String($bytes).Replace('+', '-').Replace('/', '_')
|
||||
}
|
||||
|
||||
function Ensure-NetxDataDirectories {
|
||||
param([string]$DataRoot)
|
||||
$subs = @(
|
||||
|
|
@ -201,6 +214,146 @@ function Test-NetxApiHealthy {
|
|||
}
|
||||
}
|
||||
|
||||
function Wait-NetxApiHealthy {
|
||||
param(
|
||||
[string]$HostName = "127.0.0.1",
|
||||
[int]$Port = 8890,
|
||||
[int]$TimeoutSec = 180,
|
||||
[int]$PollMs = 500
|
||||
)
|
||||
$deadline = (Get-Date).AddSeconds($TimeoutSec)
|
||||
while ((Get-Date) -lt $deadline) {
|
||||
if (Test-NetxApiHealthy -HostName $HostName -Port $Port -TimeoutSec 2) {
|
||||
return $true
|
||||
}
|
||||
Start-Sleep -Milliseconds $PollMs
|
||||
}
|
||||
return $false
|
||||
}
|
||||
|
||||
function ConvertTo-NetxProcessArgument {
|
||||
param([Parameter(Mandatory = $true)][AllowEmptyString()][string]$Value)
|
||||
# Start-Process joins string[] args with spaces and does NOT quote values that
|
||||
# contain spaces (e.g. C:\Program Files\NetX). Always emit one argv token.
|
||||
if ($Value -match '[\s"]') {
|
||||
return '"' + ($Value -replace '"', '\"') + '"'
|
||||
}
|
||||
return $Value
|
||||
}
|
||||
|
||||
function ConvertTo-NetxProcessArgumentString {
|
||||
param([Parameter(Mandatory = $true)][string[]]$Arguments)
|
||||
return (($Arguments | ForEach-Object { ConvertTo-NetxProcessArgument -Value "$_" }) -join " ")
|
||||
}
|
||||
|
||||
function Start-NetxPowerShell {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$File,
|
||||
[string[]]$Arguments = @(),
|
||||
[string]$WorkingDirectory = "",
|
||||
[ValidateSet("Hidden", "Normal", "Minimized")]
|
||||
[string]$WindowStyle = "Hidden",
|
||||
[switch]$Wait = $false,
|
||||
[switch]$PassThru = $false
|
||||
)
|
||||
$parts = @(
|
||||
"-NoProfile",
|
||||
"-WindowStyle", $WindowStyle,
|
||||
"-ExecutionPolicy", "Bypass",
|
||||
"-File", $File
|
||||
) + $Arguments
|
||||
$sp = @{
|
||||
FilePath = "powershell.exe"
|
||||
ArgumentList = (ConvertTo-NetxProcessArgumentString -Arguments $parts)
|
||||
WindowStyle = $WindowStyle
|
||||
}
|
||||
if ($WorkingDirectory) { $sp.WorkingDirectory = $WorkingDirectory }
|
||||
if ($Wait) { $sp.Wait = $true }
|
||||
if ($PassThru -or $Wait) { $sp.PassThru = $true }
|
||||
return Start-Process @sp
|
||||
}
|
||||
|
||||
function Get-NetxSystemPython {
|
||||
# Prefer a real interpreter; skip the WindowsApps Store stub.
|
||||
$candidates = @()
|
||||
foreach ($cmd in @("python", "python3", "py")) {
|
||||
$c = Get-Command $cmd -ErrorAction SilentlyContinue
|
||||
if ($c -and $c.Source -and ($c.Source -notmatch '\\WindowsApps\\')) {
|
||||
$candidates += $c.Source
|
||||
}
|
||||
}
|
||||
foreach ($p in @(
|
||||
"$env:LOCALAPPDATA\Python\pythoncore-3.14-64\python.exe",
|
||||
"$env:LOCALAPPDATA\Programs\Python\Python312\python.exe",
|
||||
"$env:LOCALAPPDATA\Programs\Python\Python311\python.exe",
|
||||
"$env:ProgramFiles\Python312\python.exe",
|
||||
"$env:ProgramFiles\Python311\python.exe"
|
||||
)) {
|
||||
if ($p -and (Test-Path $p)) { $candidates += $p }
|
||||
}
|
||||
foreach ($p in ($candidates | Select-Object -Unique)) {
|
||||
try {
|
||||
$v = & $p -c "import sys; print('%d.%d'%sys.version_info[:2])" 2>$null
|
||||
if ($v -match '^(3\.(1[1-9]|[2-9]\d))$') { return $p }
|
||||
} catch {}
|
||||
}
|
||||
return $null
|
||||
}
|
||||
|
||||
function Ensure-NetxVenv {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$ProgramRoot,
|
||||
[switch]$ForcePip = $false
|
||||
)
|
||||
$venvPy = Join-Path $ProgramRoot ".venv\Scripts\python.exe"
|
||||
$req = Join-Path $ProgramRoot "requirements.txt"
|
||||
if ((Test-Path $venvPy) -and -not $ForcePip) {
|
||||
return $venvPy
|
||||
}
|
||||
if (-not (Test-Path $venvPy)) {
|
||||
$py = Get-NetxSystemPython
|
||||
if (-not $py) {
|
||||
throw "python_not_found: install Python 3.11+ (or ship Setup built with -CreateVenv)"
|
||||
}
|
||||
Write-Host "==> Creating .venv with $py"
|
||||
$venvDir = Join-Path $ProgramRoot ".venv"
|
||||
try {
|
||||
& $py -m venv $venvDir
|
||||
} catch {
|
||||
throw "venv_create_failed: cannot write $venvDir (need admin / ship -CreateVenv). $($_.Exception.Message)"
|
||||
}
|
||||
if (-not (Test-Path $venvPy)) {
|
||||
throw "venv_create_failed: missing $venvPy (Program Files may be read-only without elevation)"
|
||||
}
|
||||
$ForcePip = $true
|
||||
}
|
||||
if ($ForcePip) {
|
||||
if (-not (Test-Path $req)) { throw "requirements_missing: $req" }
|
||||
Write-Host "==> pip install -r requirements.txt"
|
||||
& $venvPy -m pip install --upgrade pip
|
||||
& $venvPy -m pip install -r $req
|
||||
if ($LASTEXITCODE -ne 0) { throw "pip_install_failed" }
|
||||
}
|
||||
return $venvPy
|
||||
}
|
||||
|
||||
function Stop-NetxTrayProcesses {
|
||||
param([string]$ProgramRoot = "")
|
||||
$hits = @(Get-CimInstance Win32_Process -ErrorAction SilentlyContinue | Where-Object {
|
||||
$_.CommandLine -and $_.CommandLine -match 'netx_tray\.ps1'
|
||||
})
|
||||
if ($ProgramRoot) {
|
||||
$esc = [regex]::Escape($ProgramRoot)
|
||||
$hits = @($hits | Where-Object { $_.CommandLine -match $esc -or $_.CommandLine -match 'netx_tray\.ps1' })
|
||||
}
|
||||
foreach ($p in $hits) {
|
||||
try {
|
||||
Stop-Process -Id $p.ProcessId -Force -ErrorAction Stop
|
||||
Write-Host "Stopped NetX tray PID=$($p.ProcessId)"
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
|
||||
function ConvertTo-NetxSqlLiteral([string]$Value) {
|
||||
# Single-quote escaping for PostgreSQL string literals.
|
||||
return ($Value -replace "'", "''")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue