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 <cursoragent@cursor.com>
This commit is contained in:
oliver 2026-10-08 21:01:04 +08:00
parent 7b5dd3eada
commit a9c9d17550
11 changed files with 250 additions and 164 deletions

View file

@ -22,7 +22,7 @@ jobs:
with: with:
python-version: "3.12" python-version: "3.12"
- name: Build release zip - name: Build release stage
shell: pwsh shell: pwsh
run: | run: |
powershell -ExecutionPolicy Bypass -File ./packaging/build_release.ps1 -CreateVenv powershell -ExecutionPolicy Bypass -File ./packaging/build_release.ps1 -CreateVenv
@ -43,6 +43,5 @@ jobs:
uses: softprops/action-gh-release@v2 uses: softprops/action-gh-release@v2
with: with:
files: | files: |
packaging/release/NetX-*-win64.zip
packaging/release/NetX-Setup-*.exe packaging/release/NetX-Setup-*.exe
generate_release_notes: true generate_release_notes: true

View file

@ -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`. 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) ### 6) MCP(Cursor / oclaw / Claude)

View file

@ -14,12 +14,13 @@ This directory builds a Windows deliverable with:
| Artifact | How | | Artifact | How |
|----------|-----| |----------|-----|
| `NetX-x.y.z-win64.zip` | `build_release.ps1` | | `NetX-Setup-x.y.z.exe` | Stage with `build_release.ps1`, then compile `installer/netx.iss` with [Inno Setup](https://jrsoftware.org/isinfo.php) |
| `NetX-Setup-x.y.z.exe` | Compile `installer/netx.iss` with [Inno Setup](https://jrsoftware.org/isinfo.php) after staging |
Zip packages are **not** published. Releases ship **Setup.exe only**.
**Installer:** English + 简体中文 (language dialog). Icons use `packaging/assets/netx.ico`. **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. **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 # Optional: download portable Postgres into packaging\postgres\pgsql
powershell -ExecutionPolicy Bypass -File .\packaging\download_postgres.ps1 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 powershell -ExecutionPolicy Bypass -File .\packaging\build_release.ps1 -CreateVenv
``` ```
Output: Output:
- `packaging/release/netx-win64/` — stage tree - `packaging/release/netx-win64/` — stage tree (input to Inno Setup)
- `packaging/release/NetX-<ver>-win64.zip`
Inno Setup: Inno Setup:
@ -48,33 +48,25 @@ 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 ## End-user install
### Setup.exe ### Setup.exe
1. Run `NetX-Setup-x.y.z.exe` (admin). 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). 2. **First install:** 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). 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. Data → `%ProgramData%\NetX\` (`.env`, `pgdata`, spool, secrets). Post-install writes `.env` automatically. 4. Files → `%ProgramFiles%\NetX\` (program root).
5. Start menu: **Start NetX** / **Stop NetX** / **Open NetX UI** / **Reconfigure database** (repair only). 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 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. 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 ## Database modes
| Mode | Behavior | | Mode | Behavior |
@ -87,14 +79,16 @@ Existing Windows deploys that only set `NETX_DATABASE_URL` keep working: never s
## Manual update ## 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 ```powershell
.\packaging\update_netx.ps1 -PackagePath .\NetX-0.4.0-win64.zip .\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. 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 ## 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. **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 .\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) ### Maintainer release checklist (Windows)
Do this on the **dev PC on the home LAN** after bumping version: 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) 1. **Build** — `.\packaging\build_release.ps1 -CreateVenv` + Inno Setup → `NetX-Setup-*.exe`
2. **GitHub** — `.\packaging\publish_release.ps1 -Version x.y.z` (or `gh release create …`) 2. **GitHub** — `.\packaging\publish_release.ps1 -Version x.y.z` (uploads Setup.exe only)
3. **Forgejo (intranet)** — upload the same assets to QNAP Forgejo; public `git.avelo.top` is only a reverse proxy to the same instance: 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 ```powershell
# One-time: User env var (never commit the token) # One-time: User env var (never commit the token)

View file

@ -3,6 +3,9 @@ param(
[string]$OutDir = "", [string]$OutDir = "",
[switch]$SkipWebBuild = $false, [switch]$SkipWebBuild = $false,
[switch]$SkipPostgresDownload = $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, [switch]$SkipZip = $false,
# Default on: offline Setup must ship .venv so target PCs never pip-install. # Default on: offline Setup must ship .venv so target PCs never pip-install.
[switch]$CreateVenv = $true [switch]$CreateVenv = $true
@ -233,15 +236,18 @@ if ($CreateVenv) {
Write-Host "==> Stage ready: $stage" -ForegroundColor Green 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" $zip = Join-Path (Split-Path -Parent $stage) "NetX-$Version-win64.zip"
if (Test-Path $zip) { Remove-Item -Force $zip } if (Test-Path $zip) { Remove-Item -Force $zip }
Compress-Archive -Path $stage -DestinationPath $zip -Force 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 ""
Write-Host "Ship options:" Write-Host "Ship:"
Write-Host " Zip: user unpacks, runs packaging\setup_first_run.ps1 then start_netx_app.ps1"
Write-Host " Exe: compile packaging\installer\netx.iss with Inno Setup (needs stage above)" 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" Write-Host "Python: install 3.11+ on target, or rebuild with -CreateVenv and ship .venv"

View file

@ -1,4 +1,4 @@
param( param(
[string]$ProgramRoot = "", [string]$ProgramRoot = "",
[string]$DataRoot = "", [string]$DataRoot = "",
[string]$UpdateUrl = "", [string]$UpdateUrl = "",
@ -109,16 +109,32 @@ function Convert-ReleaseToManifest {
$tag = [string]$Rel.tag_name $tag = [string]$Rel.tag_name
$ver = $tag.TrimStart('v', 'V') $ver = $tag.TrimStart('v', 'V')
$assets = @($Rel.assets) $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 $setupAsset = $assets | Where-Object { $_.name -match 'Setup-.*\.exe$' } | Select-Object -First 1
if (-not $zipAsset) { throw "${SourceName}_release_missing_zip" } $zipAsset = $assets | Where-Object { $_.name -match 'win64\.zip$' } | Select-Object -First 1
if (-not $setupAsset -and -not $zipAsset) {
$zipUrl = Resolve-AssetUrl -Asset $zipAsset -Rel $Rel -SourceName $SourceName -ApiLatestUrl $ApiLatestUrl throw "${SourceName}_release_missing_setup_or_zip"
if (-not $zipUrl) { throw "${SourceName}_zip_url_missing" } }
$setupUrl = "" $setupUrl = ""
if ($setupAsset) { if ($setupAsset) {
$setupUrl = Resolve-AssetUrl -Asset $setupAsset -Rel $Rel -SourceName $SourceName -ApiLatestUrl $ApiLatestUrl $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]@{ return [pscustomobject]@{
@ -128,10 +144,11 @@ function Convert-ReleaseToManifest {
notes_url = [string]$Rel.html_url notes_url = [string]$Rel.html_url
source = $SourceName source = $SourceName
windows = [pscustomobject]@{ windows = [pscustomobject]@{
url = $zipUrl url = $primaryUrl
sha256 = "" sha256 = ""
size = [int64]$(if ($zipAsset.size) { $zipAsset.size } else { 0 }) size = $primarySize
setup_url = $setupUrl 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 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 $latest = [string]$manifest.latest
$cmp = Compare-SemVer -A $current -B $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]@{ $result = [pscustomobject]@{
current = $current current = $current
latest = $latest latest = $latest
@ -255,6 +290,7 @@ $result = [pscustomobject]@{
source = $(if ($manifest.source) { [string]$manifest.source } else { "unknown" }) source = $(if ($manifest.source) { [string]$manifest.source } else { "unknown" })
download_url = [string]$manifest.windows.url download_url = [string]$manifest.windows.url
setup_url = $(if ($manifest.windows.setup_url) { [string]$manifest.windows.setup_url } else { "" }) 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 { "" }) notes_url = $(if ($manifest.notes_url) { [string]$manifest.notes_url } else { "" })
sha256 = $(if ($manifest.windows.sha256) { [string]$manifest.windows.sha256 } else { "" }) sha256 = $(if ($manifest.windows.sha256) { [string]$manifest.windows.sha256 } else { "" })
reachable = @($candidates | ForEach-Object { "$($_.source)=$($_.latest)" }) 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 ($autoVal -match '^(1|true|yes|on)$') { $autoEnabled = $true }
if (-not $Apply) { if (-not $Apply) {
Write-Host " Download (zip): $($result.download_url)" Write-Host " Download ($($result.package)): $($result.download_url)"
if ($result.setup_url) { Write-Host " Or install: $($result.setup_url)" }
Write-Host " To apply: .\packaging\check_update.ps1 -Apply" Write-Host " To apply: .\packaging\check_update.ps1 -Apply"
if (-not $Quiet) { if (-not $Quiet) {
$result | ConvertTo-Json -Compress | Write-Output $result | ConvertTo-Json -Compress | Write-Output
@ -313,16 +348,22 @@ try {
if (-not (Test-Path $dlDir)) { if (-not (Test-Path $dlDir)) {
New-Item -ItemType Directory -Path $dlDir -Force | Out-Null 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 $needDownload = $true
if ((Test-Path -LiteralPath $zipPath) -and ((Get-Item -LiteralPath $zipPath).Length -gt 1MB)) { if ((Test-Path -LiteralPath $pkgPath) -and ((Get-Item -LiteralPath $pkgPath).Length -gt 1MB)) {
Write-Host "==> Using existing download: $zipPath ($([math]::Round((Get-Item $zipPath).Length/1MB,1)) MB)" Write-Host "==> Using existing download: $pkgPath ($([math]::Round((Get-Item $pkgPath).Length/1MB,1)) MB)"
$needDownload = $false $needDownload = $false
} }
if ($needDownload) { 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; # Only attach token for non-GitHub hosts (Forgejo/private). Public GitHub assets need no auth;
# a Forgejo token would break anonymous GitHub downloads. # a Forgejo token would break anonymous GitHub downloads.
$dlHeaders = @{ "User-Agent" = "NetX-UpdateCheck" } $dlHeaders = @{ "User-Agent" = "NetX-UpdateCheck" }
@ -331,10 +372,10 @@ try {
if ($UpdateToken -and -not $isGithub) { if ($UpdateToken -and -not $isGithub) {
$dlHeaders["Authorization"] = "token $UpdateToken" $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()) { 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() $expect = $result.sha256.ToLowerInvariant()
if ($hash -ne $expect) { if ($hash -ne $expect) {
throw "sha256_mismatch: got $hash expected $expect" throw "sha256_mismatch: got $hash expected $expect"
@ -342,7 +383,7 @@ try {
Write-Host "==> SHA256 OK" 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 $progWritable = $false
try { try {
$probe = Join-Path $prog (".netx_w_" + [guid]::NewGuid().ToString("n")) $probe = Join-Path $prog (".netx_w_" + [guid]::NewGuid().ToString("n"))
@ -361,10 +402,26 @@ try {
exit $ep.ExitCode exit $ep.ExitCode
} }
Write-Host "==> Applying update via update_netx.ps1"
$env:NETX_UPDATE_ELEVATED = "1" $env:NETX_UPDATE_ELEVATED = "1"
& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "update_netx.ps1") ` if ($isSetup) {
-PackagePath $zipPath -ProgramRoot $prog -DataRoot $data # 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 Write-Host "==> Update applied to $latest" -ForegroundColor Green
} finally { } finally {
Remove-Item -Force $lockFile -ErrorAction SilentlyContinue Remove-Item -Force $lockFile -ErrorAction SilentlyContinue

View file

@ -1,11 +1,11 @@
; NetX Windows installer (Inno Setup 6+) ; NetX Windows installer (Inno Setup 6+)
; Compile after: packaging\build_release.ps1 ; Compile after: packaging\build_release.ps1
; ISCC.exe packaging\installer\netx.iss ; ISCC.exe packaging\installer\netx.iss
; Encoding: UTF-8 with BOM (required for Chinese CustomMessages) ; Encoding: UTF-8 with BOM (required for Chinese CustomMessages)
#define MyAppName "NetX" #define MyAppName "NetX"
#ifndef MyAppVersion #ifndef MyAppVersion
#define MyAppVersion "0.4.10" #define MyAppVersion "0.4.11"
#endif #endif
#define MyAppPublisher "NetX" #define MyAppPublisher "NetX"
#define MyAppURL "https://github.com/hansjone/netx" #define MyAppURL "https://github.com/hansjone/netx"
@ -39,7 +39,7 @@ ArchitecturesAllowed=x64compatible
ArchitecturesInstallIn64BitMode=x64compatible ArchitecturesInstallIn64BitMode=x64compatible
PrivilegesRequired=admin PrivilegesRequired=admin
; Data lives under {commonappdata}\NetX - not overwritten by upgrades. ; 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. ; pages while scanning locked NetX tray/service processes with no visible dialog.
CloseApplications=no CloseApplications=no
ShowLanguageDialog=yes ShowLanguageDialog=yes
@ -66,45 +66,47 @@ english.DbUser=User
english.DbPassword=Password english.DbPassword=Password
english.DbName=Database english.DbName=Database
english.CredKey=Credential key 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.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.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.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.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.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.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.DbEnvMissing=Database configuration did not write %ProgramData%\NetX\.env.
english.ReconfigureDb=Reconfigure database english.ReconfigureDb=Reconfigure database
chinesesimplified.CreateDesktopIcon=创建桌面快捷方式 english.UpgradeKeepDb=Existing NetX data found - database settings will be kept (no wizard).
chinesesimplified.SetupOptions=安装完成后: chinesesimplified.CreateDesktopIcon=创建桌面快捷方式
chinesesimplified.StartTrayNow=立即启动 NetX 托盘 chinesesimplified.SetupOptions=安装完成后:
chinesesimplified.EnableAutostart=开机时自动启动 NetX 托盘 chinesesimplified.StartTrayNow=立即启动 NetX 托盘
chinesesimplified.EnableAutoUpdate=启用每日静默自动更新(以后需要联网) chinesesimplified.EnableAutostart=开机时自动启动 NetX 托盘
chinesesimplified.InstallService=安装为 Windows 服务(使用已捆绑的 WinSW,可离线) chinesesimplified.EnableAutoUpdate=启用每日静默自动更新(以后需要联网)
chinesesimplified.UninstallDeleteData=是否同时删除 NetX 数据(%ProgramData%\NetX)?%n%n是 = 删除数据库、配置和密钥%n否 = 保留数据以便以后重装 chinesesimplified.InstallService=安装为 Windows 服务(使用已捆绑的 WinSW,可离线)
chinesesimplified.DbPageCaption=数据库 chinesesimplified.UninstallDeleteData=是否同时删除 NetX 数据(%ProgramData%\NetX)?%n%n是 = 删除数据库、配置和密钥%n否 = 保留数据以便以后重装
chinesesimplified.DbPageDescription=选择内置或外置 PostgreSQL。可选粘贴已有 NETX_CREDENTIAL_SECRET_KEY;留空则自动生成。 chinesesimplified.DbPageCaption=数据库
chinesesimplified.DbBundled=内置 PostgreSQL(便携,可离线) chinesesimplified.DbPageDescription=选择内置或外置 PostgreSQL。可选粘贴已有 NETX_CREDENTIAL_SECRET_KEY;留空则自动生成。
chinesesimplified.DbExternal=外置 PostgreSQL(已有服务器) chinesesimplified.DbBundled=内置 PostgreSQL(便携,可离线)
chinesesimplified.DbHost=主机 chinesesimplified.DbExternal=外置 PostgreSQL(已有服务器)
chinesesimplified.DbPort=端口 chinesesimplified.DbHost=主机
chinesesimplified.DbUser=用户 chinesesimplified.DbPort=端口
chinesesimplified.DbPassword=密码 chinesesimplified.DbUser=用户
chinesesimplified.DbName=数据库名 chinesesimplified.DbPassword=密码
chinesesimplified.CredKey=凭据密钥 chinesesimplified.DbName=数据库名
chinesesimplified.CredKeyHint=NETX_CREDENTIAL_SECRET_KEY(可选,留空=自动生成) chinesesimplified.CredKey=凭据密钥
chinesesimplified.DbFieldsRequired=请填写主机、端口、用户、密码和数据库名。 chinesesimplified.CredKeyHint=NETX_CREDENTIAL_SECRET_KEY(可选,留空=自动生成)
chinesesimplified.DbTesting=正在测试 PostgreSQL 连接,请稍候… chinesesimplified.DbFieldsRequired=请填写主机、端口、用户、密码和数据库名。
chinesesimplified.DbTestFailed=无法用当前设置连接 PostgreSQL。%n%n%1%n%n请改正后重试。 chinesesimplified.DbTesting=正在测试 PostgreSQL 连接,请稍候…
chinesesimplified.DbTestExtractFailed=无法解压 PostgreSQL 客户端以测试连接。 chinesesimplified.DbTestFailed=无法用当前设置连接 PostgreSQL。%n%n%1%n%n请改正后重试。
chinesesimplified.DbSilentExternalMissing=静默安装外置库需要参数 /DbHost /DbUser /DbPassword /DbName(可选 /DbPort)。 chinesesimplified.DbTestExtractFailed=无法解压 PostgreSQL 客户端以测试连接。
chinesesimplified.DbApplyFailed=文件安装后数据库自动配置失败(退出码 %1)。%n%n日志: %2%n%n请处理后使用开始菜单 → 重新配置数据库,或重新安装。 chinesesimplified.DbSilentExternalMissing=静默安装外置库需要参数 /DbHost /DbUser /DbPassword /DbName(可选 /DbPort)。
chinesesimplified.DbApplyExecFailed=无法启动数据库配置脚本。 chinesesimplified.DbApplyFailed=文件安装后数据库自动配置失败(退出码 %1)。%n%n日志: %2%n%n请处理后使用开始菜单 → 重新配置数据库,或重新安装。
chinesesimplified.DbBundledMissing=安装目录中缺少内置 PostgreSQL 文件:%n%1%n%n安装包不完整,请重新下载 NetX-Setup 后再装。 chinesesimplified.DbApplyExecFailed=无法启动数据库配置脚本。
chinesesimplified.DbEnvMissing=数据库配置未写入 %ProgramData%\NetX\.env。 chinesesimplified.DbBundledMissing=安装目录中缺少内置 PostgreSQL 文件:%n%1%n%n安装包不完整,请重新下载 NetX-Setup 后再装。
chinesesimplified.ReconfigureDb=重新配置数据库 chinesesimplified.DbEnvMissing=数据库配置未写入 %ProgramData%\NetX\.env。
chinesesimplified.ReconfigureDb=重新配置数据库
chinesesimplified.UpgradeKeepDb=检测到已有 NetX 数据 - 将保留数据库配置(跳过向导)。
[Tasks] [Tasks]
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: checkedonce 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). ; 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 Start.lnk"
Type: files; Name: "{commondesktop}\NetX First-time setup.lnk" Type: files; Name: "{commondesktop}\NetX First-time setup.lnk"
Type: files; Name: "{commondesktop}\首次配置(内置或外置数据库).lnk" Type: files; Name: "{commondesktop}\首次配置(内置或外置数据库).lnk"
[Icons] [Icons]
; Prefer .cmd targets so Server 2012 / classic Start Menu shows normal program entries. ; Prefer .cmd targets so Server 2012 / classic Start Menu shows normal program entries.
@ -200,6 +202,7 @@ var
GDbName: String; GDbName: String;
GCredKey: String; GCredKey: String;
GSkipDbPage: Boolean; GSkipDbPage: Boolean;
GIsUpgrade: Boolean;
GDbConfigured: Boolean; GDbConfigured: Boolean;
GPgToolsReady: Boolean; GPgToolsReady: Boolean;
GDbConnOk: Boolean; GDbConnOk: Boolean;
@ -248,6 +251,37 @@ begin
end; end;
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; function NetxShouldStartTray(): Boolean;
begin begin
Result := GDbConfigured or Result := GDbConfigured or
@ -504,10 +538,16 @@ begin
GDbConfigured := False; GDbConfigured := False;
GPgToolsReady := False; GPgToolsReady := False;
GDbConnOk := False; GDbConnOk := False;
{ Always show DB page so choosing built-in always re-runs auto-configure. { Upgrade / reinstall over a configured install: skip DB wizard and keep
Skipping when .env exists caused upgrades/reinstalls to keep a broken or ProgramData\.env / pgdata. Fresh install still shows the Database page.
external config and still launch tray → confusing "first-time" prompts. } Force wizard: /ForceDbPage=1 Force skip: /SkipDbPage=1 }
GSkipDbPage := False; GIsUpgrade := DetectExistingConfiguredInstall();
if ParamIsTruthy('ForceDbPage') then
GSkipDbPage := False
else if ParamIsTruthy('SkipDbPage') then
GSkipDbPage := True
else
GSkipDbPage := GIsUpgrade;
DbPage := CreateCustomPage(wpSelectDir, DbPage := CreateCustomPage(wpSelectDir,
ExpandConstant('{cm:DbPageCaption}'), ExpandConstant('{cm:DbPageCaption}'),
@ -679,7 +719,7 @@ begin
end; end;
{ Connection test here so bad settings never reach Tasks/Ready. { 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}')); SetWizardBusy(True, ExpandConstant('{cm:DbTesting}'));
try try
Result := ValidateExternalDbSaved(ErrMsg); Result := ValidateExternalDbSaved(ErrMsg);
@ -863,7 +903,11 @@ var
begin begin
if CurStep = ssPostInstall then if CurStep = ssPostInstall then
begin 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 := UninstallKey :=
'Software\Microsoft\Windows\CurrentVersion\Uninstall\{A7E3C2D1-9F40-4B8E-9C1A-NETXWIN64001}_is1'; 'Software\Microsoft\Windows\CurrentVersion\Uninstall\{A7E3C2D1-9F40-4B8E-9C1A-NETXWIN64001}_is1';

View file

@ -1,11 +1,12 @@
{ {
"channel": "stable", "channel": "stable",
"latest": "0.4.10", "latest": "0.4.11",
"min_compatible": "0.3.0", "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": { "windows": {
"url": "https://github.com/hansjone/netx/releases/download/v0.4.10/NetX-0.4.10-win64.zip", "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.10/NetX-Setup-0.4.10.exe", "setup_url": "https://github.com/hansjone/netx/releases/download/v0.4.11/NetX-Setup-0.4.11.exe",
"package": "setup",
"sha256": "", "sha256": "",
"size": 0 "size": 0
} }

View file

@ -1,14 +1,13 @@
param( param(
[string]$Version = "", [string]$Version = "",
[string]$ForgejoBase = "http://10.0.0.131:3000", [string]$ForgejoBase = "http://10.0.0.131:3000",
[string]$Owner = "hansjone", [string]$Owner = "hansjone",
[string]$Repo = "netx", [string]$Repo = "netx",
[string]$Token = "", [string]$Token = "",
[string]$ReleaseDir = "", [string]$ReleaseDir = ""
[switch]$SkipSetup = $false
) )
# 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). # 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). # 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" $tag = "v$Version"
$relDir = if ($ReleaseDir) { $ReleaseDir } else { Join-Path $PSScriptRoot "release" } $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" $setup = Join-Path $relDir "NetX-Setup-$Version.exe"
if (-not $Token) { if (-not $Token) {
@ -38,8 +36,8 @@ if (-not $Token) {
if (-not $Token) { if (-not $Token) {
throw "forgejo_token_required: set NETX_FORGEJO_TOKEN or pass -Token" throw "forgejo_token_required: set NETX_FORGEJO_TOKEN or pass -Token"
} }
if (-not (Test-Path $zip)) { if (-not (Test-Path $setup)) {
throw "missing_zip: $zip (run build_release.ps1 first)" throw "missing_setup: $setup (run build_release.ps1 + ISCC first)"
} }
$apiBase = "$ForgejoBase/api/v1/repos/$Owner/$Repo" $apiBase = "$ForgejoBase/api/v1/repos/$Owner/$Repo"
@ -84,10 +82,9 @@ try {
} }
$notes = @" $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-Setup-$Version.exe
- NetX-$Version-win64.zip
"@ "@
if ($existing -and $existing.id) { if ($existing -and $existing.id) {
@ -97,12 +94,12 @@ if ($existing -and $existing.id) {
Write-Host "==> Creating release $tag" Write-Host "==> Creating release $tag"
$rel = Invoke-ForgejoJson -Method POST -Url "$apiBase/releases" -Body @{ $rel = Invoke-ForgejoJson -Method POST -Url "$apiBase/releases" -Body @{
tag_name = $tag tag_name = $tag
target = "main" target = "main"
name = "NetX $Version" name = "NetX $Version"
body = $notes body = $notes
draft = $false draft = $false
prerelease = $false prerelease = $false
} }
$relId = $rel.id $relId = $rel.id
if (-not $relId) { throw "forgejo_create_release_failed" } if (-not $relId) { throw "forgejo_create_release_failed" }
@ -123,10 +120,7 @@ function Upload-ForgejoAsset {
Write-Host " OK $name" -ForegroundColor Green Write-Host " OK $name" -ForegroundColor Green
} }
Upload-ForgejoAsset -Path $zip Upload-ForgejoAsset -Path $setup
if (-not $SkipSetup -and (Test-Path $setup)) {
Upload-ForgejoAsset -Path $setup
}
$releaseUrl = "$ForgejoBase/$Owner/$Repo/releases/tag/$tag" $releaseUrl = "$ForgejoBase/$Owner/$Repo/releases/tag/$tag"
Write-Host "" Write-Host ""

View file

@ -1,4 +1,4 @@
param( param(
[string]$Version = "", [string]$Version = "",
[switch]$SkipBuild = $false, [switch]$SkipBuild = $false,
[switch]$SkipInstaller = $false, [switch]$SkipInstaller = $false,
@ -16,7 +16,6 @@ if (-not $Version) {
} }
$tag = "v$Version" $tag = "v$Version"
$releaseDir = Join-Path $PSScriptRoot "release" $releaseDir = Join-Path $PSScriptRoot "release"
$zip = Join-Path $releaseDir "NetX-$Version-win64.zip"
$setup = Join-Path $releaseDir "NetX-Setup-$Version.exe" $setup = Join-Path $releaseDir "NetX-Setup-$Version.exe"
Write-Host "==> NetX publish $tag" Write-Host "==> NetX publish $tag"
@ -26,10 +25,6 @@ if (-not $SkipBuild) {
-Version $Version -CreateVenv -Version $Version -CreateVenv
} }
if (-not (Test-Path $zip)) {
throw "missing_zip: $zip"
}
if (-not $SkipInstaller) { if (-not $SkipInstaller) {
$isccCmd = Get-Command ISCC.exe -ErrorAction SilentlyContinue $isccCmd = Get-Command ISCC.exe -ErrorAction SilentlyContinue
$isccCandidates = @( $isccCandidates = @(
@ -40,27 +35,23 @@ if (-not $SkipInstaller) {
) | Where-Object { $_ -and (Test-Path $_) } ) | Where-Object { $_ -and (Test-Path $_) }
$iscc = $isccCandidates | Select-Object -First 1 $iscc = $isccCandidates | Select-Object -First 1
if (-not $iscc) { if (-not $iscc) {
Write-Host "[WARN] Inno Setup (ISCC) not found. Install: winget install JRSoftware.InnoSetup" -ForegroundColor Yellow throw "innosetup_not_found: install with winget install JRSoftware.InnoSetup"
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
} }
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) { if ($Sign) {
$toSign = @() Write-Host "==> Signing release artifacts"
if (Test-Path $setup) { $toSign += $setup } & powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "sign_release.ps1") -Files @($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
}
} }
if ($SkipGitHub) { if ($SkipGitHub) {
@ -81,27 +72,24 @@ $viewCode = $LASTEXITCODE
$ErrorActionPreference = $prevEap $ErrorActionPreference = $prevEap
if ($viewCode -eq 0) { if ($viewCode -eq 0) {
Write-Host "==> Release $tag exists; uploading assets" Write-Host "==> Release $tag exists; uploading assets"
gh release upload $tag $zip --clobber gh release upload $tag $setup --clobber
if (Test-Path $setup) {
gh release upload $tag $setup --clobber
}
} else { } else {
$notes = @" $notes = @"
## NetX $Version — first Windows installable release ## NetX $Version
### Downloads ### Downloads
- **NetX-Setup-$Version.exe** — recommended installer (Program Files + ProgramData) - **NetX-Setup-$Version.exe** — Windows installer (Program Files + ProgramData)
- **NetX-$Version-win64.zip** — portable directory package
### Requirements ### Requirements
- Windows 10/11 x64 - Windows 10/11 x64 (or Windows Server 2016+)
- No separate Python or PostgreSQL install required (bundled in package) - No separate Python or PostgreSQL install required (bundled)
### Quick start (installer) ### Quick start
1. Run ``NetX-Setup-$Version.exe`` as administrator. 1. Run ``NetX-Setup-$Version.exe`` as administrator.
2. Complete first-time setup (choose **bundled** or **external** PostgreSQL). 2. **First install:** choose built-in or external PostgreSQL.
3. Start menu → **Start NetX** → browser opens ``http://127.0.0.1:8890/`` 3. **Already installed:** Setup detects existing data and updates in place (keeps DB settings).
4. Default login: ``admin`` / ``admin123`` (change after first login) 4. Start menu → **Start NetX** → ``http://127.0.0.1:8890/``
5. Default login: ``admin`` / ``admin123`` (change after first login)
### Linux ### Linux
Unchanged — use your own PostgreSQL and ``scripts/start_netx.sh``. 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) $args = @("release", "create", $tag, "--title", "NetX $Version", "--notes", $notes)
if ($Draft) { $args += "--draft" } if ($Draft) { $args += "--draft" }
$args += $zip $args += $setup
if (Test-Path $setup) { $args += $setup }
& gh @args & gh @args
} }

View file

@ -1,4 +1,4 @@
param( param(
[Parameter(Mandatory = $true)] [Parameter(Mandatory = $true)]
[string]$PackagePath, [string]$PackagePath,
[string]$ProgramRoot = "", [string]$ProgramRoot = "",
@ -22,10 +22,14 @@ New-Item -ItemType Directory -Path $work -Force | Out-Null
try { try {
Write-Host "==> Extracting update package" 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")) { if ($PackagePath.ToLowerInvariant().EndsWith(".zip")) {
Expand-Archive -Path $PackagePath -DestinationPath $work -Force 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 { } 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 $src = $work

View file

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