From 022de81eb057cf0d17698e6cb12f6181ab8a0d78 Mon Sep 17 00:00:00 2001 From: oliver Date: Wed, 7 Oct 2026 00:56:53 +0800 Subject: [PATCH] Add Forgejo LAN release publish script and maintainer checklist. Default upload target is home Forgejo (10.0.0.131); mirror sync does not copy GitHub assets. Co-authored-by: Cursor --- packaging/README.md | 38 +++++++- packaging/build_release.ps1 | 2 +- packaging/publish_forgejo_release.ps1 | 134 ++++++++++++++++++++++++++ 3 files changed, 172 insertions(+), 2 deletions(-) create mode 100644 packaging/publish_forgejo_release.ps1 diff --git a/packaging/README.md b/packaging/README.md index fdc805a..a71f0de 100644 --- a/packaging/README.md +++ b/packaging/README.md @@ -110,7 +110,43 @@ 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 Release 也需要挂上安装包(或仅用 GitHub 下包)。 +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). + +### 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: + +```powershell +# One-time: User env var (never commit the token) +# Forgejo → Settings → Applications → Generate New Token (repo write) +[Environment]::SetEnvironmentVariable("NETX_FORGEJO_TOKEN", "your_token", "User") +# Restart Cursor / open a new terminal so the agent/scripts see it + +# Default publishes to LAN Forgejo; public git.avelo.top shows the same Release. +.\packaging\publish_forgejo_release.ps1 -Version 0.4.0 + +# Only when off the home LAN: +# .\packaging\publish_forgejo_release.ps1 -Version 0.4.0 -ForgejoBase "https://git.avelo.top" +``` + +| Role | URL | +|------|-----| +| Publish default (home LAN) | `http://10.0.0.131:3000` (`-ForgejoBase` default) | +| Clients / remote clone | `https://git.avelo.top` (Caddy → `10.0.0.131:3000`) | +| Update probe (default) | GitHub + `https://git.avelo.top/.../releases/latest` | + +Verify: + +- LAN: http://10.0.0.131:3000/hansjone/netx/releases +- Public: https://git.avelo.top/hansjone/netx/releases +- Probe: `.\packaging\check_update.ps1` → both `github` and `forgejo` reachable with the new version + +Token: `NETX_FORGEJO_TOKEN` (or `FORGEJO_TOKEN`). + ## Tray & autostart ```powershell diff --git a/packaging/build_release.ps1 b/packaging/build_release.ps1 index 5d391db..d62968a 100644 --- a/packaging/build_release.ps1 +++ b/packaging/build_release.ps1 @@ -74,7 +74,7 @@ foreach ($name in @( "stop_netx_app.ps1", "update_netx.ps1", "check_update.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", "README.md", "manifest.example.json" + "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 } diff --git a/packaging/publish_forgejo_release.ps1 b/packaging/publish_forgejo_release.ps1 new file mode 100644 index 0000000..40fd352 --- /dev/null +++ b/packaging/publish_forgejo_release.ps1 @@ -0,0 +1,134 @@ +param( + [string]$Version = "", + [string]$ForgejoBase = "http://10.0.0.131:3000", + [string]$Owner = "hansjone", + [string]$Repo = "netx", + [string]$Token = "", + [string]$ReleaseDir = "", + [switch]$SkipSetup = $false +) + +# Publish Windows release assets 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). +# +# Example (usual — LAN): +# $env:NETX_FORGEJO_TOKEN = "..." # or User env NETX_FORGEJO_TOKEN +# .\packaging\publish_forgejo_release.ps1 -Version 0.4.0 +# +# Away from LAN only (hairpins via VPS): +# .\packaging\publish_forgejo_release.ps1 -Version 0.4.0 -ForgejoBase "https://git.avelo.top" + +$ErrorActionPreference = "Stop" +. "$PSScriptRoot\_common.ps1" + +$repoRoot = Get-NetxRepoRoot +if (-not $Version) { + $Version = Get-NetxVersion -ProgramRoot $repoRoot +} +$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) { + $Token = $env:NETX_FORGEJO_TOKEN + if (-not $Token) { $Token = $env:FORGEJO_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)" +} + +$apiBase = "$ForgejoBase/api/v1/repos/$Owner/$Repo" +$headers = @{ + "Authorization" = "token $Token" + "Accept" = "application/json" +} + +function Invoke-ForgejoJson { + param( + [string]$Method, + [string]$Url, + [object]$Body = $null + ) + $params = @{ + Method = $Method + Uri = $Url + Headers = $headers + ContentType = "application/json" + } + if ($null -ne $Body) { + $params["Body"] = ($Body | ConvertTo-Json -Depth 5) + } + return Invoke-RestMethod @params +} + +Write-Host "==> Forgejo publish $tag -> $ForgejoBase/$Owner/$Repo" + +# Ensure git tag exists on Forgejo (mirror usually already has it). +try { + $null = Invoke-ForgejoJson -Method GET -Url "$apiBase/git/refs/tags/$tag" + Write-Host " tag $tag present on Forgejo" +} catch { + Write-Host "[WARN] tag $tag not found on Forgejo yet — run mirror-sync or push tag first" -ForegroundColor Yellow +} + +$existing = $null +try { + $existing = Invoke-ForgejoJson -Method GET -Url "$apiBase/releases/tags/$tag" +} catch { + $existing = $null +} + +$notes = @" +NetX $Version Windows install package (published from GitHub release build). + +- NetX-Setup-$Version.exe +- NetX-$Version-win64.zip +"@ + +if ($existing -and $existing.id) { + Write-Host "==> Release $tag already exists (id=$($existing.id)); deleting old release to re-upload assets" + Invoke-ForgejoJson -Method DELETE -Url "$apiBase/releases/$($existing.id)" | Out-Null +} + +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 +} +$relId = $rel.id +if (-not $relId) { throw "forgejo_create_release_failed" } + +function Upload-ForgejoAsset { + param([string]$Path) + if (-not (Test-Path $Path)) { return } + $name = Split-Path -Leaf $Path + Write-Host "==> Uploading $name ..." + $url = "$apiBase/releases/$relId/assets" + $curl = Get-Command curl.exe -ErrorAction SilentlyContinue + if (-not $curl) { throw "curl.exe required for asset upload" } + & $curl.Source -f -sS -X POST ` + -H "Authorization: token $Token" ` + -F "attachment=@$Path" ` + $url + if ($LASTEXITCODE -ne 0) { throw "upload_failed: $name" } + Write-Host " OK $name" -ForegroundColor Green +} + +Upload-ForgejoAsset -Path $zip +if (-not $SkipSetup -and (Test-Path $setup)) { + Upload-ForgejoAsset -Path $setup +} + +$releaseUrl = "$ForgejoBase/$Owner/$Repo/releases/tag/$tag" +Write-Host "" +Write-Host "==> Forgejo release ready: $releaseUrl" -ForegroundColor Green +Write-Host " Update API: $ForgejoBase/api/v1/repos/$Owner/$Repo/releases/latest"