From 41ab4921a07c3d281f9c910749c434d0c7285f6f Mon Sep 17 00:00:00 2001 From: oliver Date: Wed, 7 Oct 2026 10:53:29 +0800 Subject: [PATCH] Fix Windows PowerShell parsing on Server 2012: UTF-8 BOM scripts. Chinese packaging scripts without BOM were misread as ANSI and failed at first-run. Document that bundled Python 3.13+ needs Server 2016+ / Win10+. Co-authored-by: Cursor --- packaging/README.md | 2 ++ packaging/_common.ps1 | 2 +- packaging/build_release.ps1 | 22 ++++++++++++++++++++-- packaging/check_update.ps1 | 2 +- packaging/download_postgres.ps1 | 2 +- packaging/install_autostart.ps1 | 2 +- packaging/install_service.ps1 | 2 +- packaging/install_update_task.ps1 | 2 +- packaging/installer/netx.iss | 2 +- packaging/launch_shortcut.ps1 | 2 +- packaging/manifest.example.json | 10 +++++----- packaging/netx_tray.ps1 | 2 +- packaging/publish_forgejo_release.ps1 | 2 +- packaging/publish_release.ps1 | 2 +- packaging/service_run.ps1 | 2 +- packaging/setup_first_run.ps1 | 2 +- packaging/sign_release.ps1 | 2 +- packaging/start_netx_app.ps1 | 2 +- packaging/stop_netx_app.ps1 | 2 +- packaging/update_netx.ps1 | 2 +- pyproject.toml | 2 +- scripts/init_pg.ps1 | 2 +- scripts/start_netx.ps1 | 2 +- scripts/stop_netx.ps1 | 2 +- 24 files changed, 48 insertions(+), 28 deletions(-) diff --git a/packaging/README.md b/packaging/README.md index 3da2d65..9703c61 100644 --- a/packaging/README.md +++ b/packaging/README.md @@ -21,6 +21,8 @@ This directory builds a Windows deliverable with: **Offline:** Setup ships portable PostgreSQL, `.venv` (when built with `-CreateVenv`), and WinSW. First-run uses `-NonInteractive -DbMode bundled` — **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. + ## Build (developer machine) Prerequisites: Python 3.11+, Node 20+, PowerShell, network (to download PG binaries once). diff --git a/packaging/_common.ps1 b/packaging/_common.ps1 index 4bba789..9f5636f 100644 --- a/packaging/_common.ps1 +++ b/packaging/_common.ps1 @@ -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" diff --git a/packaging/build_release.ps1 b/packaging/build_release.ps1 index b8fc5c4..7580f15 100644 --- a/packaging/build_release.ps1 +++ b/packaging/build_release.ps1 @@ -1,4 +1,4 @@ -param( +param( [string]$Version = "", [string]$OutDir = "", [switch]$SkipWebBuild = $false, @@ -80,6 +80,22 @@ $webOut = Join-Path $stage "web\dist" New-Item -ItemType Directory -Path (Split-Path $webOut -Parent) -Force | Out-Null Copy-Item -Path $dist -Destination $webOut -Recurse -Force +function Write-Utf8BomFile { + param([string]$Path) + # Windows PowerShell 4/5 (Server 2012+) mis-parses UTF-8 without BOM when scripts contain CJK. + $utf8Bom = New-Object System.Text.UTF8Encoding $true + $bytes = [IO.File]::ReadAllBytes($Path) + $hasBom = ($bytes.Length -ge 3 -and $bytes[0] -eq 0xEF -and $bytes[1] -eq 0xBB -and $bytes[2] -eq 0xBF) + $text = if ($hasBom) { + [Text.Encoding]::UTF8.GetString($bytes, 3, $bytes.Length - 3) + } else { + [Text.Encoding]::UTF8.GetString($bytes) + } + $text = $text -replace "`r`n", "`n" -replace "`n", "`r`n" + if (-not $text.EndsWith("`r`n")) { $text += "`r`n" } + [IO.File]::WriteAllText($Path, $text, $utf8Bom) +} + $packOut = Join-Path $stage "packaging" New-Item -ItemType Directory -Path $packOut -Force | Out-Null Copy-Item -Path (Join-Path $PSScriptRoot "_common.ps1") -Destination $packOut -Force @@ -87,13 +103,15 @@ foreach ($name in @( "download_postgres.ps1", "setup_first_run.ps1", "start_netx_app.ps1", "stop_netx_app.ps1", "update_netx.ps1", "check_update.ps1", "launch_shortcut.ps1", "netx_tray.ps1", "install_autostart.ps1", "install_service.ps1", - "service_run.ps1", "install_update_task.ps1", "sign_release.ps1", "build_release.ps1", "publish_release.ps1", "publish_forgejo_release.ps1", "README.md", "manifest.example.json" )) { $src = Join-Path $PSScriptRoot $name if (Test-Path $src) { Copy-Item $src (Join-Path $packOut $name) -Force } } +Get-ChildItem -Path $packOut -Filter *.ps1 -File | ForEach-Object { Write-Utf8BomFile -Path $_.FullName } +Get-ChildItem -Path (Join-Path $stage "scripts") -Filter *.ps1 -File -ErrorAction SilentlyContinue | + ForEach-Object { Write-Utf8BomFile -Path $_.FullName } Copy-Item -Path (Join-Path $PSScriptRoot "config") -Destination (Join-Path $packOut "config") -Recurse -Force Copy-Item -Path (Join-Path $PSScriptRoot "installer") -Destination (Join-Path $packOut "installer") -Recurse -Force $assetsSrc = Join-Path $PSScriptRoot "assets" diff --git a/packaging/check_update.ps1 b/packaging/check_update.ps1 index 75f552f..7f90413 100644 --- a/packaging/check_update.ps1 +++ b/packaging/check_update.ps1 @@ -1,4 +1,4 @@ -param( +param( [string]$ProgramRoot = "", [string]$DataRoot = "", [string]$UpdateUrl = "", diff --git a/packaging/download_postgres.ps1 b/packaging/download_postgres.ps1 index 38c7b41..6398fd1 100644 --- a/packaging/download_postgres.ps1 +++ b/packaging/download_postgres.ps1 @@ -1,4 +1,4 @@ -param( +param( [string]$Version = "16.4-1", [string]$OutDir = "" ) diff --git a/packaging/install_autostart.ps1 b/packaging/install_autostart.ps1 index d5c2ebd..50510df 100644 --- a/packaging/install_autostart.ps1 +++ b/packaging/install_autostart.ps1 @@ -1,4 +1,4 @@ -param( +param( [string]$ProgramRoot = "", [switch]$Remove = $false ) diff --git a/packaging/install_service.ps1 b/packaging/install_service.ps1 index ff97f17..f9e87f6 100644 --- a/packaging/install_service.ps1 +++ b/packaging/install_service.ps1 @@ -1,4 +1,4 @@ -param( +param( [string]$ProgramRoot = "", [string]$DataRoot = "", [ValidateSet("winsw", "task")] diff --git a/packaging/install_update_task.ps1 b/packaging/install_update_task.ps1 index 9abd2c3..07b3ab9 100644 --- a/packaging/install_update_task.ps1 +++ b/packaging/install_update_task.ps1 @@ -1,4 +1,4 @@ -param( +param( [string]$ProgramRoot = "", [string]$DataRoot = "", [ValidateSet("Daily", "AtLogOn", "Hourly")] diff --git a/packaging/installer/netx.iss b/packaging/installer/netx.iss index 0c68bdb..2ee015d 100644 --- a/packaging/installer/netx.iss +++ b/packaging/installer/netx.iss @@ -4,7 +4,7 @@ #define MyAppName "NetX" #ifndef MyAppVersion - #define MyAppVersion "0.4.2" + #define MyAppVersion "0.4.3" #endif #define MyAppPublisher "NetX" #define MyAppURL "https://github.com/hansjone/netx" diff --git a/packaging/launch_shortcut.ps1 b/packaging/launch_shortcut.ps1 index f9f9da7..fd9e12d 100644 --- a/packaging/launch_shortcut.ps1 +++ b/packaging/launch_shortcut.ps1 @@ -1,4 +1,4 @@ -param( +param( [ValidateSet("tray", "start", "stop", "setup", "update")] [string]$Action = "tray", [string]$ProgramRoot = "", diff --git a/packaging/manifest.example.json b/packaging/manifest.example.json index 3e49db5..06459d3 100644 --- a/packaging/manifest.example.json +++ b/packaging/manifest.example.json @@ -1,11 +1,11 @@ -{ +{ "channel": "stable", - "latest": "0.4.2", + "latest": "0.4.3", "min_compatible": "0.3.0", - "notes_url": "https://github.com/hansjone/netx/releases/tag/v0.4.2", + "notes_url": "https://github.com/hansjone/netx/releases/tag/v0.4.3", "windows": { - "url": "https://github.com/hansjone/netx/releases/download/v0.4.2/NetX-0.4.2-win64.zip", - "setup_url": "https://github.com/hansjone/netx/releases/download/v0.4.2/NetX-Setup-0.4.2.exe", + "url": "https://github.com/hansjone/netx/releases/download/v0.4.3/NetX-0.4.3-win64.zip", + "setup_url": "https://github.com/hansjone/netx/releases/download/v0.4.3/NetX-Setup-0.4.3.exe", "sha256": "", "size": 0 } diff --git a/packaging/netx_tray.ps1 b/packaging/netx_tray.ps1 index 7c19b4f..e822fa7 100644 --- a/packaging/netx_tray.ps1 +++ b/packaging/netx_tray.ps1 @@ -1,4 +1,4 @@ -param( +param( [string]$ProgramRoot = "", [string]$DataRoot = "", [switch]$StartOnLaunch = $true, diff --git a/packaging/publish_forgejo_release.ps1 b/packaging/publish_forgejo_release.ps1 index 40fd352..da5629d 100644 --- a/packaging/publish_forgejo_release.ps1 +++ b/packaging/publish_forgejo_release.ps1 @@ -1,4 +1,4 @@ -param( +param( [string]$Version = "", [string]$ForgejoBase = "http://10.0.0.131:3000", [string]$Owner = "hansjone", diff --git a/packaging/publish_release.ps1 b/packaging/publish_release.ps1 index 19fa044..6d9e648 100644 --- a/packaging/publish_release.ps1 +++ b/packaging/publish_release.ps1 @@ -1,4 +1,4 @@ -param( +param( [string]$Version = "", [switch]$SkipBuild = $false, [switch]$SkipInstaller = $false, diff --git a/packaging/service_run.ps1 b/packaging/service_run.ps1 index 40cc61f..e514352 100644 --- a/packaging/service_run.ps1 +++ b/packaging/service_run.ps1 @@ -1,4 +1,4 @@ -param( +param( [string]$ProgramRoot = "", [string]$DataRoot = "", [int]$HealthFailLimit = 6, diff --git a/packaging/setup_first_run.ps1 b/packaging/setup_first_run.ps1 index 8889308..1a268a7 100644 --- a/packaging/setup_first_run.ps1 +++ b/packaging/setup_first_run.ps1 @@ -1,4 +1,4 @@ -param( +param( [ValidateSet("bundled", "external", "")] [string]$DbMode = "", [string]$ProgramRoot = "", diff --git a/packaging/sign_release.ps1 b/packaging/sign_release.ps1 index 121c8d9..a859447 100644 --- a/packaging/sign_release.ps1 +++ b/packaging/sign_release.ps1 @@ -1,4 +1,4 @@ -param( +param( [Parameter(Mandatory = $true)] [string[]]$Files, [string]$Thumbprint = "", diff --git a/packaging/start_netx_app.ps1 b/packaging/start_netx_app.ps1 index d57781c..6a80f9d 100644 --- a/packaging/start_netx_app.ps1 +++ b/packaging/start_netx_app.ps1 @@ -1,4 +1,4 @@ -param( +param( [string]$ProgramRoot = "", [string]$DataRoot = "", [switch]$SkipBrowser = $false, diff --git a/packaging/stop_netx_app.ps1 b/packaging/stop_netx_app.ps1 index 174c631..4d04f19 100644 --- a/packaging/stop_netx_app.ps1 +++ b/packaging/stop_netx_app.ps1 @@ -1,4 +1,4 @@ -param( +param( [string]$ProgramRoot = "", [string]$DataRoot = "", [switch]$KeepPostgres = $false diff --git a/packaging/update_netx.ps1 b/packaging/update_netx.ps1 index c959f66..0f12cc5 100644 --- a/packaging/update_netx.ps1 +++ b/packaging/update_netx.ps1 @@ -1,4 +1,4 @@ -param( +param( [Parameter(Mandatory = $true)] [string]$PackagePath, [string]$ProgramRoot = "", diff --git a/pyproject.toml b/pyproject.toml index 8e30807..ee59d1a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "netx-ops" -version = "0.4.2" +version = "0.4.3" description = "netx operations tool: alarm-centric workflows with REST API and stdio MCP" readme = "README.md" requires-python = ">=3.11" diff --git a/scripts/init_pg.ps1 b/scripts/init_pg.ps1 index d9c02de..cfcb909 100644 --- a/scripts/init_pg.ps1 +++ b/scripts/init_pg.ps1 @@ -1,4 +1,4 @@ -param( +param( [string]$PgHost = "127.0.0.1", [int]$PgPort = 5432, [string]$SuperUser = "postgres", diff --git a/scripts/start_netx.ps1 b/scripts/start_netx.ps1 index ed2caec..e89fcaf 100644 --- a/scripts/start_netx.ps1 +++ b/scripts/start_netx.ps1 @@ -1,4 +1,4 @@ -param( +param( [string]$BindHost = "127.0.0.1", [int]$Port = 8890, [int]$WebPort = 5173, diff --git a/scripts/stop_netx.ps1 b/scripts/stop_netx.ps1 index 2d91466..2e930f0 100644 --- a/scripts/stop_netx.ps1 +++ b/scripts/stop_netx.ps1 @@ -1,4 +1,4 @@ -param( +param( [int]$Port = 8890, [int]$WebPort = 5173, # Windows often ignores graceful Stop-Process on python; default hard-kill.