From a9c9d175507d33617773b7fdaa61445d710f18ca Mon Sep 17 00:00:00 2001 From: oliver Date: Thu, 8 Oct 2026 21:01:04 +0800 Subject: [PATCH] Release 0.4.11: Setup-only updates that preserve existing DB config. Offline Setup and check_update now upgrade in place without the database wizard; zip packages are no longer published. Co-authored-by: Cursor --- .github/workflows/release-windows.yml | 3 +- README.md | 4 +- packaging/README.md | 54 +++++------ packaging/build_release.ps1 | 14 ++- packaging/check_update.ps1 | 99 +++++++++++++++----- packaging/installer/netx.iss | 126 +++++++++++++++++--------- packaging/manifest.example.json | 9 +- packaging/publish_forgejo_release.ps1 | 32 +++---- packaging/publish_release.ps1 | 63 +++++-------- packaging/update_netx.ps1 | 8 +- pyproject.toml | 2 +- 11 files changed, 250 insertions(+), 164 deletions(-) diff --git a/.github/workflows/release-windows.yml b/.github/workflows/release-windows.yml index 12e2fa9..8e2ad3b 100644 --- a/.github/workflows/release-windows.yml +++ b/.github/workflows/release-windows.yml @@ -22,7 +22,7 @@ jobs: with: python-version: "3.12" - - name: Build release zip + - name: Build release stage shell: pwsh run: | powershell -ExecutionPolicy Bypass -File ./packaging/build_release.ps1 -CreateVenv @@ -43,6 +43,5 @@ jobs: uses: softprops/action-gh-release@v2 with: files: | - packaging/release/NetX-*-win64.zip packaging/release/NetX-Setup-*.exe generate_release_notes: true diff --git a/README.md b/README.md index 2ce7550..8a4d5e9 100644 --- a/README.md +++ b/README.md @@ -158,9 +158,9 @@ API base: `http://127.0.0.1:8890/` After `npm run build` in `web/`, the API can also serve the UI at `http://127.0.0.1:8890/` (same origin). See `NETX_UI_DIST_DIR` in `.env.example`. -### Windows installer / portable package (optional) +### Windows installer (optional) -Fool-proof Windows delivery (bundled or external Postgres, program/data split, manual update) lives under [`packaging/`](packaging/README.md). **Linux installs are unchanged** — keep using your own Postgres and `scripts/start_netx.sh`. +Fool-proof Windows delivery via `NetX-Setup-*.exe` (bundled or external Postgres, program/data split, offline upgrade over existing installs) lives under [`packaging/`](packaging/README.md). **Linux installs are unchanged** — keep using your own Postgres and `scripts/start_netx.sh`. ### 6) MCP(Cursor / oclaw / Claude) diff --git a/packaging/README.md b/packaging/README.md index 951db52..e9f7041 100644 --- a/packaging/README.md +++ b/packaging/README.md @@ -14,12 +14,13 @@ This directory builds a Windows deliverable with: | Artifact | How | |----------|-----| -| `NetX-x.y.z-win64.zip` | `build_release.ps1` | -| `NetX-Setup-x.y.z.exe` | Compile `installer/netx.iss` with [Inno Setup](https://jrsoftware.org/isinfo.php) after staging | +| `NetX-Setup-x.y.z.exe` | Stage with `build_release.ps1`, then compile `installer/netx.iss` with [Inno Setup](https://jrsoftware.org/isinfo.php) | + +Zip packages are **not** published. Releases ship **Setup.exe only**. **Installer:** English + 简体中文 (language dialog). Icons use `packaging/assets/netx.ico`. -**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. +**Offline:** Setup ships portable PostgreSQL, **`python/runtime` + `.venv`** (portable; not tied to the build PC’s user profile), and WinSW. **First install** shows the Database page (built-in or external PostgreSQL; external credentials validated with `psql SELECT 1`). **Re-running Setup when `%ProgramData%\NetX\.env` already has `NETX_DB_MODE`** skips the DB wizard and updates program files in place — **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. @@ -32,14 +33,13 @@ cd netx # Optional: download portable Postgres into packaging\postgres\pgsql powershell -ExecutionPolicy Bypass -File .\packaging\download_postgres.ps1 -# Build web + stage + zip (-CreateVenv ships a ready .venv; large) +# Build web + stage (-CreateVenv ships a ready .venv; large). No zip by default. powershell -ExecutionPolicy Bypass -File .\packaging\build_release.ps1 -CreateVenv ``` Output: -- `packaging/release/netx-win64/` — stage tree -- `packaging/release/NetX--win64.zip` +- `packaging/release/netx-win64/` — stage tree (input to Inno Setup) Inno Setup: @@ -47,34 +47,26 @@ Inno Setup: ISCC.exe packaging\installer\netx.iss ``` -Override version in the `.iss` or edit `#define MyAppVersion`. +Override version in the `.iss` or edit `#define MyAppVersion`. +Optional local zip only: `build_release.ps1 -CreateZip` (not for release upload). ## End-user install ### Setup.exe 1. Run `NetX-Setup-x.y.z.exe` (admin). -2. On the **Database** page: choose built-in (offline) or external PostgreSQL (host/port/user/password/db; connection must succeed to continue). -3. Files → `%ProgramFiles%\NetX\` (program root). -4. Data → `%ProgramData%\NetX\` (`.env`, `pgdata`, spool, secrets). Post-install writes `.env` automatically. -5. Start menu: **Start NetX** / **Stop NetX** / **Open NetX UI** / **Reconfigure database** (repair only). +2. **First install:** Database page — choose built-in (offline) or external PostgreSQL (host/port/user/password/db; connection must succeed to continue). +3. **Already installed (upgrade):** if `%ProgramData%\NetX\.env` already has `NETX_DB_MODE`, Setup **skips** the Database page, stops running NetX, replaces program files, and **keeps** data / DB settings. Use Start Menu → **Reconfigure database** only when you need to change DB mode. +4. Files → `%ProgramFiles%\NetX\` (program root). +5. Data → `%ProgramData%\NetX\` (`.env`, `pgdata`, spool, secrets). +6. Start menu: **Start NetX** / **Stop NetX** / **Open NetX UI** / **Reconfigure database**. -Silent (bundled default): `/SILENT /DbMode=bundled` +Silent first install (bundled default): `/SILENT /DbMode=bundled` Silent external: `/SILENT /DbMode=external /DbHost=... /DbPort=5432 /DbUser=... /DbPassword=... /DbName=...` +Silent upgrade over existing data: `/VERYSILENT /NORESTART` (auto-detects `.env`; or force `/SkipDbPage=1`) +Force DB wizard on an existing install: `/ForceDbPage=1` Optional credential key (reuse encrypted NE passwords from another install): `/CredentialSecretKey=...` — omit to auto-generate. -### Zip (portable) - -1. Unpack anywhere. -2. Marker `.portable` → data root is sibling `NetXData\`. -3. Target needs **Python 3.11+** on PATH unless the zip was built with `-CreateVenv`. -4. Run: - -```powershell -.\packaging\setup_first_run.ps1 -.\packaging\start_netx_app.ps1 -``` - ## Database modes | Mode | Behavior | @@ -87,14 +79,16 @@ Existing Windows deploys that only set `NETX_DATABASE_URL` keep working: never s ## Manual update +Preferred: run a newer `NetX-Setup-*.exe` over the same machine (upgrade mode keeps ProgramData). + +Legacy/dev zip (only if you built with `-CreateZip`): + ```powershell .\packaging\update_netx.ps1 -PackagePath .\NetX-0.4.0-win64.zip ``` Stops services, replaces program folders (`netx_api`, `web`, `packaging`, `postgres`, …), **keeps** the data root, restarts. Schema migrations still run via Alembic on API start. -Or reinstall a newer `NetX-Setup-*.exe` over the same program directory. - ## Check / apply updates **Defaults (no `.env` needed):** probe **GitHub** (`hansjone/netx`) and Forgejo mirror (`https://git.avelo.top/hansjone/netx`), then use the **newest reachable** version. Same version → prefer GitHub. @@ -120,15 +114,15 @@ Or reinstall a newer `NetX-Setup-*.exe` over the same program directory. .\packaging\check_update.ps1 -Apply ``` -Both sides should publish the same release assets (`NetX-*-win64.zip`). **Code mirror alone is not enough** — Forgejo pull-mirror syncs git/tags only; Release zip/exe must be uploaded separately (or clients can only fall back to GitHub). +Both sides should publish the same **Setup.exe**. `check_update.ps1` prefers `NetX-Setup-*.exe` (legacy `win64.zip` still works if present). **Code mirror alone is not enough** — Forgejo pull-mirror syncs git/tags only; Release assets must be uploaded separately (or clients can only fall back to GitHub). ### Maintainer release checklist (Windows) Do this on the **dev PC on the home LAN** after bumping version: -1. **Build** — `.\packaging\build_release.ps1 -CreateVenv` (and Inno Setup for Setup.exe if needed) -2. **GitHub** — `.\packaging\publish_release.ps1 -Version x.y.z` (or `gh release create …`) -3. **Forgejo (intranet)** — upload the same assets to QNAP Forgejo; public `git.avelo.top` is only a reverse proxy to the same instance: +1. **Build** — `.\packaging\build_release.ps1 -CreateVenv` + Inno Setup → `NetX-Setup-*.exe` +2. **GitHub** — `.\packaging\publish_release.ps1 -Version x.y.z` (uploads Setup.exe only) +3. **Forgejo (intranet)** — upload the same Setup.exe to QNAP Forgejo; public `git.avelo.top` is only a reverse proxy to the same instance: ```powershell # One-time: User env var (never commit the token) diff --git a/packaging/build_release.ps1 b/packaging/build_release.ps1 index d737114..10d2bcd 100644 --- a/packaging/build_release.ps1 +++ b/packaging/build_release.ps1 @@ -3,6 +3,9 @@ param( [string]$OutDir = "", [switch]$SkipWebBuild = $false, [switch]$SkipPostgresDownload = $false, + # Zip is no longer published; stage + Setup.exe only. Opt-in for local/dev testing. + [switch]$CreateZip = $false, + # Deprecated no-op (zip is off by default). Kept so older scripts that pass -SkipZip still run. [switch]$SkipZip = $false, # Default on: offline Setup must ship .venv so target PCs never pip-install. [switch]$CreateVenv = $true @@ -233,15 +236,18 @@ if ($CreateVenv) { Write-Host "==> Stage ready: $stage" -ForegroundColor Green -if (-not $SkipZip) { +if ($SkipZip -and $CreateZip) { + Write-Host "[WARN] -SkipZip ignored because -CreateZip was set" -ForegroundColor Yellow +} +if ($CreateZip) { $zip = Join-Path (Split-Path -Parent $stage) "NetX-$Version-win64.zip" if (Test-Path $zip) { Remove-Item -Force $zip } Compress-Archive -Path $stage -DestinationPath $zip -Force - Write-Host "==> Zip: $zip" -ForegroundColor Green + Write-Host "==> Zip (optional/dev): $zip" -ForegroundColor Yellow } Write-Host "" -Write-Host "Ship options:" -Write-Host " Zip: user unpacks, runs packaging\setup_first_run.ps1 then start_netx_app.ps1" +Write-Host "Ship:" Write-Host " Exe: compile packaging\installer\netx.iss with Inno Setup (needs stage above)" +Write-Host " (Zip packages are not published; use -CreateZip only for local testing.)" Write-Host "Python: install 3.11+ on target, or rebuild with -CreateVenv and ship .venv" diff --git a/packaging/check_update.ps1 b/packaging/check_update.ps1 index 87536b2..893d6e8 100644 --- a/packaging/check_update.ps1 +++ b/packaging/check_update.ps1 @@ -1,4 +1,4 @@ -param( +param( [string]$ProgramRoot = "", [string]$DataRoot = "", [string]$UpdateUrl = "", @@ -109,16 +109,32 @@ function Convert-ReleaseToManifest { $tag = [string]$Rel.tag_name $ver = $tag.TrimStart('v', 'V') $assets = @($Rel.assets) - $zipAsset = $assets | Where-Object { $_.name -match 'win64\.zip$' } | Select-Object -First 1 + # Prefer Setup.exe (current ship format). Legacy win64.zip still accepted. $setupAsset = $assets | Where-Object { $_.name -match 'Setup-.*\.exe$' } | Select-Object -First 1 - if (-not $zipAsset) { throw "${SourceName}_release_missing_zip" } - - $zipUrl = Resolve-AssetUrl -Asset $zipAsset -Rel $Rel -SourceName $SourceName -ApiLatestUrl $ApiLatestUrl - if (-not $zipUrl) { throw "${SourceName}_zip_url_missing" } + $zipAsset = $assets | Where-Object { $_.name -match 'win64\.zip$' } | Select-Object -First 1 + if (-not $setupAsset -and -not $zipAsset) { + throw "${SourceName}_release_missing_setup_or_zip" + } $setupUrl = "" if ($setupAsset) { $setupUrl = Resolve-AssetUrl -Asset $setupAsset -Rel $Rel -SourceName $SourceName -ApiLatestUrl $ApiLatestUrl + if (-not $setupUrl) { throw "${SourceName}_setup_url_missing" } + } + $zipUrl = "" + if ($zipAsset) { + $zipUrl = Resolve-AssetUrl -Asset $zipAsset -Rel $Rel -SourceName $SourceName -ApiLatestUrl $ApiLatestUrl + } + + if ($setupUrl) { + $primaryUrl = $setupUrl + $packageKind = "setup" + $primarySize = [int64]$(if ($setupAsset.size) { $setupAsset.size } else { 0 }) + } else { + if (-not $zipUrl) { throw "${SourceName}_zip_url_missing" } + $primaryUrl = $zipUrl + $packageKind = "zip" + $primarySize = [int64]$(if ($zipAsset.size) { $zipAsset.size } else { 0 }) } return [pscustomobject]@{ @@ -128,10 +144,11 @@ function Convert-ReleaseToManifest { notes_url = [string]$Rel.html_url source = $SourceName windows = [pscustomobject]@{ - url = $zipUrl + url = $primaryUrl sha256 = "" - size = [int64]$(if ($zipAsset.size) { $zipAsset.size } else { 0 }) + size = $primarySize setup_url = $setupUrl + package = $packageKind } } } @@ -246,8 +263,26 @@ foreach ($c in $candidates) { Write-Host "==> Selected source=$($manifest.source) latest=$($manifest.latest) (from $($candidates.Count) reachable)" -ForegroundColor Green +# Prefer Setup.exe when a manifest still lists a legacy zip as windows.url but also has setup_url. +if ($manifest.windows -and $manifest.windows.setup_url) { + $setupCandidate = [string]$manifest.windows.setup_url + $urlNow = [string]$manifest.windows.url + $pkgNow = if ($manifest.windows.package) { [string]$manifest.windows.package } else { "" } + if ($setupCandidate -and ($pkgNow -eq "zip" -or $urlNow -match '\.zip(\?|$)' -or -not $urlNow)) { + $manifest.windows | Add-Member -NotePropertyName url -NotePropertyValue $setupCandidate -Force + $manifest.windows | Add-Member -NotePropertyName package -NotePropertyValue "setup" -Force + } +} + $latest = [string]$manifest.latest $cmp = Compare-SemVer -A $current -B $latest +$packageKind = "setup" +if ($manifest.windows.package) { + $packageKind = [string]$manifest.windows.package +} elseif ([string]$manifest.windows.url -match '\.zip(\?|$)') { + $packageKind = "zip" +} + $result = [pscustomobject]@{ current = $current latest = $latest @@ -255,6 +290,7 @@ $result = [pscustomobject]@{ source = $(if ($manifest.source) { [string]$manifest.source } else { "unknown" }) download_url = [string]$manifest.windows.url setup_url = $(if ($manifest.windows.setup_url) { [string]$manifest.windows.setup_url } else { "" }) + package = $packageKind notes_url = $(if ($manifest.notes_url) { [string]$manifest.notes_url } else { "" }) sha256 = $(if ($manifest.windows.sha256) { [string]$manifest.windows.sha256 } else { "" }) reachable = @($candidates | ForEach-Object { "$($_.source)=$($_.latest)" }) @@ -276,8 +312,7 @@ $autoVal = Get-Cfg "NETX_UPDATE_AUTO" "" if ($autoVal -match '^(1|true|yes|on)$') { $autoEnabled = $true } if (-not $Apply) { - Write-Host " Download (zip): $($result.download_url)" - if ($result.setup_url) { Write-Host " Or install: $($result.setup_url)" } + Write-Host " Download ($($result.package)): $($result.download_url)" Write-Host " To apply: .\packaging\check_update.ps1 -Apply" if (-not $Quiet) { $result | ConvertTo-Json -Compress | Write-Output @@ -313,16 +348,22 @@ try { if (-not (Test-Path $dlDir)) { New-Item -ItemType Directory -Path $dlDir -Force | Out-Null } - $zipName = "NetX-$latest-win64.zip" - $zipPath = Join-Path $dlDir $zipName + + $isSetup = ($result.package -eq "setup") -or ($result.download_url -match '\.exe(\?|$)') + if ($isSetup) { + $pkgName = "NetX-Setup-$latest.exe" + } else { + $pkgName = "NetX-$latest-win64.zip" + } + $pkgPath = Join-Path $dlDir $pkgName $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)" + if ((Test-Path -LiteralPath $pkgPath) -and ((Get-Item -LiteralPath $pkgPath).Length -gt 1MB)) { + Write-Host "==> Using existing download: $pkgPath ($([math]::Round((Get-Item $pkgPath).Length/1MB,1)) MB)" $needDownload = $false } if ($needDownload) { - Write-Host "==> Downloading $zipName from $($result.source) ..." + Write-Host "==> Downloading $pkgName 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" } @@ -331,10 +372,10 @@ try { 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 $pkgPath -Headers $dlHeaders -UseBasicParsing } if ($result.sha256 -and $result.sha256 -notmatch 'REPLACE' -and $result.sha256.Trim()) { - $hash = (Get-FileHash -Path $zipPath -Algorithm SHA256).Hash.ToLowerInvariant() + $hash = (Get-FileHash -Path $pkgPath -Algorithm SHA256).Hash.ToLowerInvariant() $expect = $result.sha256.ToLowerInvariant() if ($hash -ne $expect) { throw "sha256_mismatch: got $hash expected $expect" @@ -342,7 +383,7 @@ try { Write-Host "==> SHA256 OK" } - # Program Files installs need admin to replace files; elevate once (avoid loop via NETX_UPDATE_ELEVATED). + # Program Files installs need admin; elevate once (avoid loop via NETX_UPDATE_ELEVATED). $progWritable = $false try { $probe = Join-Path $prog (".netx_w_" + [guid]::NewGuid().ToString("n")) @@ -361,10 +402,26 @@ try { 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 + if ($isSetup) { + # Silent Setup over an existing configured install skips the DB wizard + # (see installer/netx.iss GSkipDbPage) and preserves ProgramData. + Write-Host "==> Applying update via silent Setup.exe" + $setupArgs = "/VERYSILENT /NORESTART /SUPPRESSMSGBOXES /DIR=`"$prog`" /SkipDbPage=1" + $sp = Start-Process -FilePath $pkgPath -ArgumentList $setupArgs -Wait -PassThru + if ($null -eq $sp.ExitCode -or $sp.ExitCode -ne 0) { + $code = if ($null -eq $sp.ExitCode) { "null" } else { $sp.ExitCode } + throw "setup_update_failed: exit $code" + } + Write-Host "==> Restarting NetX after Setup" + & (Join-Path $PSScriptRoot "start_netx_app.ps1") ` + -ProgramRoot $prog -DataRoot $data -SkipBrowser + if ($LASTEXITCODE -and $LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + } else { + Write-Host "==> Applying legacy zip update via update_netx.ps1" + & powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "update_netx.ps1") ` + -PackagePath $pkgPath -ProgramRoot $prog -DataRoot $data + } Write-Host "==> Update applied to $latest" -ForegroundColor Green } finally { Remove-Item -Force $lockFile -ErrorAction SilentlyContinue diff --git a/packaging/installer/netx.iss b/packaging/installer/netx.iss index d1943c3..7e4a20e 100644 --- a/packaging/installer/netx.iss +++ b/packaging/installer/netx.iss @@ -1,11 +1,11 @@ -; NetX Windows installer (Inno Setup 6+) +; NetX Windows installer (Inno Setup 6+) ; Compile after: packaging\build_release.ps1 ; ISCC.exe packaging\installer\netx.iss ; Encoding: UTF-8 with BOM (required for Chinese CustomMessages) #define MyAppName "NetX" #ifndef MyAppVersion - #define MyAppVersion "0.4.10" + #define MyAppVersion "0.4.11" #endif #define MyAppPublisher "NetX" #define MyAppURL "https://github.com/hansjone/netx" @@ -39,7 +39,7 @@ ArchitecturesAllowed=x64compatible ArchitecturesInstallIn64BitMode=x64compatible PrivilegesRequired=admin ; Data lives under {commonappdata}\NetX - not overwritten by upgrades. -; Do NOT use CloseApplications=yes ¡ª it can freeze/beep on the Tasks/Ready +; Do NOT use CloseApplications=yes — it can freeze/beep on the Tasks/Ready ; pages while scanning locked NetX tray/service processes with no visible dialog. CloseApplications=no ShowLanguageDialog=yes @@ -66,45 +66,47 @@ english.DbUser=User english.DbPassword=Password english.DbName=Database english.CredKey=Credential key -english.CredKeyHint=NETX_CREDENTIAL_SECRET_KEY (optional ¡ª empty = auto-generate) +english.CredKeyHint=NETX_CREDENTIAL_SECRET_KEY (optional — empty = auto-generate) english.DbFieldsRequired=Please fill in host, port, user, password, and database name. -english.DbTesting=Testing PostgreSQL connection, please wait¡­ +english.DbTesting=Testing PostgreSQL connection, please wait… english.DbTestFailed=Cannot connect to PostgreSQL with these settings.%n%n%1%n%nFix the settings and try again. english.DbTestExtractFailed=Could not extract PostgreSQL client tools for connection test. english.DbSilentExternalMissing=Silent install with external DB requires /DbHost /DbUser /DbPassword /DbName (optional /DbPort). -english.DbApplyFailed=Database configuration failed after file install (exit %1).%n%nLog: %2%n%nFix the problem, then use Start Menu ¡ú Reconfigure database ¡ª or reinstall. +english.DbApplyFailed=Database configuration failed after file install (exit %1).%n%nLog: %2%n%nFix the problem, then use Start Menu → Reconfigure database — or reinstall. english.DbApplyExecFailed=Could not start database configuration script. english.DbBundledMissing=Built-in PostgreSQL files are missing from the install folder:%n%1%n%nThe Setup package is incomplete. Re-download NetX-Setup and install again. english.DbEnvMissing=Database configuration did not write %ProgramData%\NetX\.env. english.ReconfigureDb=Reconfigure database -chinesesimplified.CreateDesktopIcon=´´½¨×ÀÃæ¿ì½Ý·½Ê½ -chinesesimplified.SetupOptions=°²×°Íê³Éºó: -chinesesimplified.StartTrayNow=Á¢¼´Æô¶¯ NetX ÍÐÅÌ -chinesesimplified.EnableAutostart=¿ª»úʱ×Ô¶¯Æô¶¯ NetX ÍÐÅÌ -chinesesimplified.EnableAutoUpdate=ÆôÓÃÿÈÕ¾²Ä¬×Ô¶¯¸üУ¨ÒÔºóÐèÒªÁªÍø£© -chinesesimplified.InstallService=°²×°Îª Windows ·þÎñ£¨Ê¹ÓÃÒÑÀ¦°óµÄ WinSW£¬¿ÉÀëÏߣ© -chinesesimplified.UninstallDeleteData=ÊÇ·ñͬʱɾ³ý NetX Êý¾Ý£¨%ProgramData%\NetX£©£¿%n%nÊÇ = ɾ³ýÊý¾Ý¿â¡¢ÅäÖúÍÃÜÔ¿%n·ñ = ±£ÁôÊý¾ÝÒÔ±ãÒÔºó֨װ -chinesesimplified.DbPageCaption=Êý¾Ý¿â -chinesesimplified.DbPageDescription=Ñ¡ÔñÄÚÖûòÍâÖà PostgreSQL¡£¿ÉѡճÌùÒÑÓÐ NETX_CREDENTIAL_SECRET_KEY£»Áô¿ÕÔò×Ô¶¯Éú³É¡£ -chinesesimplified.DbBundled=ÄÚÖà PostgreSQL£¨±ãЯ£¬¿ÉÀëÏߣ© -chinesesimplified.DbExternal=ÍâÖà PostgreSQL£¨ÒÑÓзþÎñÆ÷£© -chinesesimplified.DbHost=Ö÷»ú -chinesesimplified.DbPort=¶Ë¿Ú -chinesesimplified.DbUser=Óû§ -chinesesimplified.DbPassword=ÃÜÂë -chinesesimplified.DbName=Êý¾Ý¿âÃû -chinesesimplified.CredKey=ƾ¾ÝÃÜÔ¿ -chinesesimplified.CredKeyHint=NETX_CREDENTIAL_SECRET_KEY£¨¿ÉÑ¡£¬Áô¿Õ=×Ô¶¯Éú³É£© -chinesesimplified.DbFieldsRequired=ÇëÌîдÖ÷»ú¡¢¶Ë¿Ú¡¢Óû§¡¢ÃÜÂëºÍÊý¾Ý¿âÃû¡£ -chinesesimplified.DbTesting=ÕýÔÚ²âÊÔ PostgreSQL Á¬½Ó£¬ÇëÉÔºò¡­ -chinesesimplified.DbTestFailed=ÎÞ·¨Óõ±Ç°ÉèÖÃÁ¬½Ó PostgreSQL¡£%n%n%1%n%nÇë¸ÄÕýºóÖØÊÔ¡£ -chinesesimplified.DbTestExtractFailed=ÎÞ·¨½âѹ PostgreSQL ¿Í»§¶ËÒÔ²âÊÔÁ¬½Ó¡£ -chinesesimplified.DbSilentExternalMissing=¾²Ä¬°²×°ÍâÖÿâÐèÒª²ÎÊý /DbHost /DbUser /DbPassword /DbName£¨¿ÉÑ¡ /DbPort£©¡£ -chinesesimplified.DbApplyFailed=Îļþ°²×°ºóÊý¾Ý¿â×Ô¶¯ÅäÖÃʧ°Ü£¨Í˳öÂë %1£©¡£%n%nÈÕÖ¾: %2%n%nÇë´¦ÀíºóʹÓÿªÊ¼²Ëµ¥ ¡ú ÖØÐÂÅäÖÃÊý¾Ý¿â£¬»òÖØÐ°²×°¡£ -chinesesimplified.DbApplyExecFailed=ÎÞ·¨Æô¶¯Êý¾Ý¿âÅäÖýű¾¡£ -chinesesimplified.DbBundledMissing=°²×°Ä¿Â¼ÖÐȱÉÙÄÚÖà PostgreSQL Îļþ£º%n%1%n%n°²×°°ü²»ÍêÕû£¬ÇëÖØÐÂÏÂÔØ NetX-Setup ºóÔÙ×°¡£ -chinesesimplified.DbEnvMissing=Êý¾Ý¿âÅäÖÃδдÈë %ProgramData%\NetX\.env¡£ -chinesesimplified.ReconfigureDb=ÖØÐÂÅäÖÃÊý¾Ý¿â +english.UpgradeKeepDb=Existing NetX data found - database settings will be kept (no wizard). +chinesesimplified.CreateDesktopIcon=创建桌é�¢å¿«æ�·æ–¹å¼� +chinesesimplified.SetupOptions=安装完æˆ�å�Ž: +chinesesimplified.StartTrayNow=ç«‹å�³å�¯åЍ NetX 托盘 +chinesesimplified.EnableAutostart=开机时自动å�¯åЍ NetX 托盘 +chinesesimplified.EnableAutoUpdate=å�¯ç”¨æ¯�æ—¥é�™é»˜è‡ªåŠ¨æ›´æ–°ï¼ˆä»¥å�Žéœ€è¦�è�”网) +chinesesimplified.InstallService=安装为 Windows æœ�务(使用已æ�†ç»‘çš„ WinSW,å�¯ç¦»çº¿ï¼‰ +chinesesimplified.UninstallDeleteData=是å�¦å�Œæ—¶åˆ é™¤ NetX æ•°æ�®ï¼ˆ%ProgramData%\NetX)?%n%n是 = 删除数æ�®åº“ã€�é…�置和密钥%nå�¦ = ä¿�留数æ�®ä»¥ä¾¿ä»¥å�Žé‡�装 +chinesesimplified.DbPageCaption=æ•°æ�®åº“ +chinesesimplified.DbPageDescription=选择内置或外置 PostgreSQL。å�¯é€‰ç²˜è´´å·²æœ‰ NETX_CREDENTIAL_SECRET_KEY;留空则自动生æˆ�。 +chinesesimplified.DbBundled=内置 PostgreSQL(便æ�ºï¼Œå�¯ç¦»çº¿ï¼‰ +chinesesimplified.DbExternal=外置 PostgreSQL(已有æœ�务器) +chinesesimplified.DbHost=主机 +chinesesimplified.DbPort=端å�£ +chinesesimplified.DbUser=用户 +chinesesimplified.DbPassword=密ç � +chinesesimplified.DbName=æ•°æ�®åº“å�� +chinesesimplified.CredKey=凭æ�®å¯†é’¥ +chinesesimplified.CredKeyHint=NETX_CREDENTIAL_SECRET_KEY(å�¯é€‰ï¼Œç•™ç©º=自动生æˆ�) +chinesesimplified.DbFieldsRequired=请填写主机ã€�端å�£ã€�用户ã€�密ç �和数æ�®åº“å��。 +chinesesimplified.DbTesting=正在测试 PostgreSQL 连接,请ç¨�候… +chinesesimplified.DbTestFailed=无法用当å‰�设置连接 PostgreSQL。%n%n%1%n%n请改正å�Žé‡�试。 +chinesesimplified.DbTestExtractFailed=无法解压 PostgreSQL 客户端以测试连接。 +chinesesimplified.DbSilentExternalMissing=é�™é»˜å®‰è£…外置库需è¦�å�‚æ•° /DbHost /DbUser /DbPassword /DbName(å�¯é€‰ /DbPort)。 +chinesesimplified.DbApplyFailed=文件安装å�Žæ•°æ�®åº“自动é…�置失败(退出ç � %1)。%n%n日志: %2%n%n请处ç�†å�Žä½¿ç”¨å¼€å§‹è�œå�• → é‡�æ–°é…�置数æ�®åº“,或é‡�新安装。 +chinesesimplified.DbApplyExecFailed=无法å�¯åŠ¨æ•°æ�®åº“é…�置脚本。 +chinesesimplified.DbBundledMissing=安装目录中缺少内置 PostgreSQL 文件:%n%1%n%n安装包ä¸�完整,请é‡�新下载 NetX-Setup å�Žå†�装。 +chinesesimplified.DbEnvMissing=æ•°æ�®åº“é…�置未写入 %ProgramData%\NetX\.env。 +chinesesimplified.ReconfigureDb=é‡�æ–°é…�置数æ�®åº“ +chinesesimplified.UpgradeKeepDb=检测到已有 NetX æ•°æ�® - å°†ä¿�留数æ�®åº“é…�置(跳过å�‘导)。 [Tasks] Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: checkedonce @@ -140,7 +142,7 @@ Name: "{commonappdata}\NetX\backups"; Permissions: users-modify ; Remove legacy desktop shortcuts from older Setup builds (keep a single NetX icon). Type: files; Name: "{commondesktop}\NetX Start.lnk" Type: files; Name: "{commondesktop}\NetX First-time setup.lnk" -Type: files; Name: "{commondesktop}\Ê×´ÎÅäÖã¨ÄÚÖûòÍâÖÃÊý¾Ý¿â£©.lnk" +Type: files; Name: "{commondesktop}\首次é…�置(内置或外置数æ�®åº“).lnk" [Icons] ; Prefer .cmd targets so Server 2012 / classic Start Menu shows normal program entries. @@ -200,6 +202,7 @@ var GDbName: String; GCredKey: String; GSkipDbPage: Boolean; + GIsUpgrade: Boolean; GDbConfigured: Boolean; GPgToolsReady: Boolean; GDbConnOk: Boolean; @@ -248,6 +251,37 @@ begin end; end; +function ParamIsTruthy(const ParamName: String): Boolean; +var + V: String; +begin + V := LowerCase(Trim(ExpandConstant('{param:' + ParamName + '|}'))); + Result := (V = '1') or (V = 'true') or (V = 'yes') or (V = 'on'); +end; + +function DetectExistingConfiguredInstall(): Boolean; +begin + { Configured install = ProgramData\.env already has NETX_DB_MODE. } + Result := EnvHasDbMode(); +end; + +procedure StopNetxBeforeFileReplace(); +var + ResultCode: Integer; + StopScript: String; + Params: String; +begin + StopScript := ExpandConstant('{app}\packaging\stop_netx_app.ps1'); + if not FileExists(StopScript) then + Exit; + Params := + '-NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -File "' + StopScript + '"' + + ' -ProgramRoot "' + ExpandConstant('{app}') + '"' + + ' -DataRoot "' + ExpandConstant('{commonappdata}\NetX') + '"' + + ' -KillTray'; + Exec('powershell.exe', Params, ExpandConstant('{app}'), SW_HIDE, ewWaitUntilTerminated, ResultCode); +end; + function NetxShouldStartTray(): Boolean; begin Result := GDbConfigured or @@ -504,10 +538,16 @@ begin GDbConfigured := False; GPgToolsReady := False; GDbConnOk := False; - { Always show DB page so choosing built-in always re-runs auto-configure. - Skipping when .env exists caused upgrades/reinstalls to keep a broken or - external config and still launch tray ¡ú confusing "first-time" prompts. } - GSkipDbPage := False; + { Upgrade / reinstall over a configured install: skip DB wizard and keep + ProgramData\.env / pgdata. Fresh install still shows the Database page. + Force wizard: /ForceDbPage=1 Force skip: /SkipDbPage=1 } + GIsUpgrade := DetectExistingConfiguredInstall(); + if ParamIsTruthy('ForceDbPage') then + GSkipDbPage := False + else if ParamIsTruthy('SkipDbPage') then + GSkipDbPage := True + else + GSkipDbPage := GIsUpgrade; DbPage := CreateCustomPage(wpSelectDir, ExpandConstant('{cm:DbPageCaption}'), @@ -679,7 +719,7 @@ begin end; { Connection test here so bad settings never reach Tasks/Ready. - Show busy state ¡ª ExtractTemporaryFile + psql can take a few seconds. } + Show busy state — ExtractTemporaryFile + psql can take a few seconds. } SetWizardBusy(True, ExpandConstant('{cm:DbTesting}')); try Result := ValidateExternalDbSaved(ErrMsg); @@ -863,7 +903,11 @@ var begin if CurStep = ssPostInstall then begin - ApplyDatabaseConfig(); + { Upgrade keeps existing .env / pgdata - do not re-run DB wizard. } + if not GSkipDbPage then + ApplyDatabaseConfig() + else + GDbConfigured := FileExists(ExpandConstant('{commonappdata}\NetX\.env')); UninstallKey := 'Software\Microsoft\Windows\CurrentVersion\Uninstall\{A7E3C2D1-9F40-4B8E-9C1A-NETXWIN64001}_is1'; diff --git a/packaging/manifest.example.json b/packaging/manifest.example.json index 0e978b8..36ac2c8 100644 --- a/packaging/manifest.example.json +++ b/packaging/manifest.example.json @@ -1,11 +1,12 @@ { "channel": "stable", - "latest": "0.4.10", + "latest": "0.4.11", "min_compatible": "0.3.0", - "notes_url": "https://github.com/hansjone/netx/releases/tag/v0.4.10", + "notes_url": "https://github.com/hansjone/netx/releases/tag/v0.4.11", "windows": { - "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", + "url": "https://github.com/hansjone/netx/releases/download/v0.4.11/NetX-Setup-0.4.11.exe", + "setup_url": "https://github.com/hansjone/netx/releases/download/v0.4.11/NetX-Setup-0.4.11.exe", + "package": "setup", "sha256": "", "size": 0 } diff --git a/packaging/publish_forgejo_release.ps1 b/packaging/publish_forgejo_release.ps1 index da5629d..6fe9d11 100644 --- a/packaging/publish_forgejo_release.ps1 +++ b/packaging/publish_forgejo_release.ps1 @@ -1,14 +1,13 @@ -param( +param( [string]$Version = "", [string]$ForgejoBase = "http://10.0.0.131:3000", [string]$Owner = "hansjone", [string]$Repo = "netx", [string]$Token = "", - [string]$ReleaseDir = "", - [switch]$SkipSetup = $false + [string]$ReleaseDir = "" ) -# Publish Windows release assets to Forgejo/Gitea (mirror sync does NOT copy GitHub Release files). +# Publish Windows Setup.exe to Forgejo/Gitea (mirror sync does NOT copy GitHub Release files). # Requires a Forgejo token with repo write (Settings → Applications → Generate New Token). # Default: home LAN Forgejo. Public git.avelo.top is the same instance (VPS reverse proxy). # @@ -28,7 +27,6 @@ if (-not $Version) { } $tag = "v$Version" $relDir = if ($ReleaseDir) { $ReleaseDir } else { Join-Path $PSScriptRoot "release" } -$zip = Join-Path $relDir "NetX-$Version-win64.zip" $setup = Join-Path $relDir "NetX-Setup-$Version.exe" if (-not $Token) { @@ -38,8 +36,8 @@ if (-not $Token) { if (-not $Token) { throw "forgejo_token_required: set NETX_FORGEJO_TOKEN or pass -Token" } -if (-not (Test-Path $zip)) { - throw "missing_zip: $zip (run build_release.ps1 first)" +if (-not (Test-Path $setup)) { + throw "missing_setup: $setup (run build_release.ps1 + ISCC first)" } $apiBase = "$ForgejoBase/api/v1/repos/$Owner/$Repo" @@ -84,10 +82,9 @@ try { } $notes = @" -NetX $Version Windows install package (published from GitHub release build). +NetX $Version Windows installer (published from GitHub release build). - NetX-Setup-$Version.exe -- NetX-$Version-win64.zip "@ if ($existing -and $existing.id) { @@ -97,12 +94,12 @@ if ($existing -and $existing.id) { Write-Host "==> Creating release $tag" $rel = Invoke-ForgejoJson -Method POST -Url "$apiBase/releases" -Body @{ - tag_name = $tag - target = "main" - name = "NetX $Version" - body = $notes - draft = $false - prerelease = $false + tag_name = $tag + target = "main" + name = "NetX $Version" + body = $notes + draft = $false + prerelease = $false } $relId = $rel.id if (-not $relId) { throw "forgejo_create_release_failed" } @@ -123,10 +120,7 @@ function Upload-ForgejoAsset { Write-Host " OK $name" -ForegroundColor Green } -Upload-ForgejoAsset -Path $zip -if (-not $SkipSetup -and (Test-Path $setup)) { - Upload-ForgejoAsset -Path $setup -} +Upload-ForgejoAsset -Path $setup $releaseUrl = "$ForgejoBase/$Owner/$Repo/releases/tag/$tag" Write-Host "" diff --git a/packaging/publish_release.ps1 b/packaging/publish_release.ps1 index 6d9e648..490de7b 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, @@ -16,7 +16,6 @@ if (-not $Version) { } $tag = "v$Version" $releaseDir = Join-Path $PSScriptRoot "release" -$zip = Join-Path $releaseDir "NetX-$Version-win64.zip" $setup = Join-Path $releaseDir "NetX-Setup-$Version.exe" Write-Host "==> NetX publish $tag" @@ -26,10 +25,6 @@ if (-not $SkipBuild) { -Version $Version -CreateVenv } -if (-not (Test-Path $zip)) { - throw "missing_zip: $zip" -} - if (-not $SkipInstaller) { $isccCmd = Get-Command ISCC.exe -ErrorAction SilentlyContinue $isccCandidates = @( @@ -40,27 +35,23 @@ if (-not $SkipInstaller) { ) | Where-Object { $_ -and (Test-Path $_) } $iscc = $isccCandidates | Select-Object -First 1 if (-not $iscc) { - Write-Host "[WARN] Inno Setup (ISCC) not found. Install: winget install JRSoftware.InnoSetup" -ForegroundColor Yellow - Write-Host " Skipping Setup.exe; zip-only release." - } else { - Write-Host "==> Compiling installer: $iscc" - & $iscc "/DMyAppVersion=$Version" (Join-Path $PSScriptRoot "installer\netx.iss") - if (-not (Test-Path $setup)) { - throw "installer_build_failed: expected $setup" - } - Write-Host "==> Setup.exe: $setup" -ForegroundColor Green + throw "innosetup_not_found: install with winget install JRSoftware.InnoSetup" } + Write-Host "==> Compiling installer: $iscc" + & $iscc "/DMyAppVersion=$Version" (Join-Path $PSScriptRoot "installer\netx.iss") + if (-not (Test-Path $setup)) { + throw "installer_build_failed: expected $setup" + } + Write-Host "==> Setup.exe: $setup" -ForegroundColor Green +} + +if (-not (Test-Path $setup)) { + throw "missing_setup: $setup" } if ($Sign) { - $toSign = @() - if (Test-Path $setup) { $toSign += $setup } - if ($toSign.Count -eq 0) { - Write-Host "[WARN] -Sign set but no Setup.exe to sign" - } else { - Write-Host "==> Signing release artifacts" - & powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "sign_release.ps1") -Files $toSign - } + Write-Host "==> Signing release artifacts" + & powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "sign_release.ps1") -Files @($setup) } if ($SkipGitHub) { @@ -81,27 +72,24 @@ $viewCode = $LASTEXITCODE $ErrorActionPreference = $prevEap if ($viewCode -eq 0) { Write-Host "==> Release $tag exists; uploading assets" - gh release upload $tag $zip --clobber - if (Test-Path $setup) { - gh release upload $tag $setup --clobber - } + gh release upload $tag $setup --clobber } else { $notes = @" -## NetX $Version — first Windows installable release +## NetX $Version ### Downloads -- **NetX-Setup-$Version.exe** — recommended installer (Program Files + ProgramData) -- **NetX-$Version-win64.zip** — portable directory package +- **NetX-Setup-$Version.exe** — Windows installer (Program Files + ProgramData) ### Requirements -- Windows 10/11 x64 -- No separate Python or PostgreSQL install required (bundled in package) +- Windows 10/11 x64 (or Windows Server 2016+) +- No separate Python or PostgreSQL install required (bundled) -### Quick start (installer) +### Quick start 1. Run ``NetX-Setup-$Version.exe`` as administrator. -2. Complete first-time setup (choose **bundled** or **external** PostgreSQL). -3. Start menu → **Start NetX** → browser opens ``http://127.0.0.1:8890/`` -4. Default login: ``admin`` / ``admin123`` (change after first login) +2. **First install:** choose built-in or external PostgreSQL. +3. **Already installed:** Setup detects existing data and updates in place (keeps DB settings). +4. Start menu → **Start NetX** → ``http://127.0.0.1:8890/`` +5. Default login: ``admin`` / ``admin123`` (change after first login) ### Linux Unchanged — use your own PostgreSQL and ``scripts/start_netx.sh``. @@ -110,8 +98,7 @@ See [packaging/README.md](https://github.com/hansjone/netx/blob/main/packaging/R "@ $args = @("release", "create", $tag, "--title", "NetX $Version", "--notes", $notes) if ($Draft) { $args += "--draft" } - $args += $zip - if (Test-Path $setup) { $args += $setup } + $args += $setup & gh @args } diff --git a/packaging/update_netx.ps1 b/packaging/update_netx.ps1 index 616ebf6..8efc50b 100644 --- a/packaging/update_netx.ps1 +++ b/packaging/update_netx.ps1 @@ -1,4 +1,4 @@ -param( +param( [Parameter(Mandatory = $true)] [string]$PackagePath, [string]$ProgramRoot = "", @@ -22,10 +22,14 @@ New-Item -ItemType Directory -Path $work -Force | Out-Null try { Write-Host "==> Extracting update package" + # Preferred end-user path is NetX-Setup-*.exe (check_update / offline installer). + # This script remains for legacy/dev .zip packages (build_release.ps1 -CreateZip). if ($PackagePath.ToLowerInvariant().EndsWith(".zip")) { Expand-Archive -Path $PackagePath -DestinationPath $work -Force + } elseif ($PackagePath.ToLowerInvariant().EndsWith(".exe")) { + throw "unsupported_package: run NetX-Setup-*.exe (upgrade keeps DB), or use check_update.ps1 -Apply" } else { - throw "unsupported_package: use a .zip built by build_release.ps1" + throw "unsupported_package: use a .zip from build_release.ps1 -CreateZip, or NetX-Setup-*.exe" } $src = $work diff --git a/pyproject.toml b/pyproject.toml index 4c6493f..4278ff3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "netx-ops" -version = "0.4.10" +version = "0.4.11" description = "netx operations tool: alarm-centric workflows with REST API and stdio MCP" readme = "README.md" requires-python = ">=3.11"